├── .gitignore ├── AuthorsSignature.txt ├── Drivers ├── Accelerometer │ ├── Accelerometer.py │ ├── README.md │ └── __init__.py ├── Driver.py ├── Magnetometer │ ├── Magnetometer.py │ ├── README.md │ └── __init__.py ├── README.md ├── UV │ ├── README.md │ ├── UVDriver.py │ └── __init__.py ├── __init__.py ├── adc │ ├── ADC_Driver.py │ ├── ADC_Driver.py.save │ ├── README.md │ └── __init__.py ├── antennaDoor │ ├── AntennaDoor.py │ ├── README.md │ └── __init__.py ├── backupAntennaDeployer │ ├── BackupAntennaDeployer.py │ ├── README.md │ └── __init__.py ├── boomDeployer │ ├── BoomDeployer.py │ ├── README.md │ └── __init__.py ├── camera │ ├── .Camera.py.swo │ ├── Camera.py │ ├── README.md │ └── __init__.py ├── cpuTemperature │ ├── CpuTemperature.py │ ├── README.md │ └── __init__.py ├── eps │ ├── EPS.py │ ├── EPS.py.save │ ├── README.md │ ├── __init__.py │ └── documentation.txt ├── rtc │ ├── README.md │ ├── __init__.py │ └── rtc_driver.py ├── solarPanelTemp │ ├── README.md │ ├── __init__.py │ └── solarDriver.py ├── sunSensors │ ├── README.md │ ├── __init__.py │ └── sunSensorDriver.py └── transceiverConfig │ ├── README.md │ ├── TransceiverConfig.py │ └── __init__.py ├── DummyDrivers ├── Accelerometer │ ├── Accelerometer.py │ ├── README.md │ └── __init__.py ├── Driver.py ├── Magnetometer │ ├── Magnetometer.py │ ├── README.md │ └── __init__.py ├── README.md ├── UV │ ├── README.md │ ├── UVDriver.py │ └── __init__.py ├── __init__.py ├── adc │ ├── ADC_Driver.py │ ├── README.md │ └── __init__.py ├── antennaDoor │ ├── AntennaDoor.py │ ├── README.md │ └── __init__.py ├── backupAntennaDeployer │ ├── BackupAntennaDeployer.py │ ├── README.md │ └── __init__.py ├── boomDeployer │ ├── BoomDeployer.py │ ├── README.md │ └── __init__.py ├── camera │ ├── .Camera.py.swo │ ├── Camera.py │ ├── README.md │ └── __init__.py ├── cpuTemperature │ ├── CpuTemperature.py │ ├── README.md │ └── __init__.py ├── eps │ ├── EPS.py │ ├── README.md │ ├── __init__.py │ └── documentation.txt ├── rtc │ ├── README.md │ ├── __init__.py │ └── rtc_driver.py ├── solarPanelTemp │ ├── README.md │ ├── __init__.py │ └── solarDriver.py ├── sunSensors │ ├── README.md │ ├── __init__.py │ └── sunSensorDriver.py └── transceiverConfig │ ├── README.md │ ├── TransceiverConfig.py │ └── __init__.py ├── README.md ├── TXISR ├── InformationDocs │ ├── TXServiceCode User Guide.docx │ ├── TXServiceCodeTest │ └── TXServiceCode_User_Guide.txt ├── README.md ├── TXServiceCode │ ├── TXService.run │ ├── TXServiceCode.c │ ├── TXServiceCodeV1.map │ ├── debug.h │ └── readMe ├── __init__.py ├── exampleFiles │ ├── exampleTXFileData.txt │ └── exampleTXFilePicture.txt ├── packetProcessing.py ├── prepareFiles.py ├── pythonInterrupt.py └── transmitionQueue.py ├── __init__.py ├── changelog ├── docs ├── CubeWorks_UML.png ├── DataBase schema Proposal.docx ├── Electrical_notes.md ├── README.md └── testing.md ├── flightConfig.c ├── flightConfigWifi.c ├── flightLogic ├── DummygetDriverData.py ├── DummymainFlightLogic.py ├── DummymissionModes │ ├── README.md │ ├── antennaDeploy.py │ ├── boomDeploy.py │ ├── postBoomDeploy.py │ ├── preBoomDeploy.py │ └── safe.py ├── README.md ├── flightLogicPsuedo.md ├── getDriverData.py ├── mainFlightLogic.py ├── missionModes │ ├── README.md │ ├── antennaDeploy.py │ ├── boomDeploy.py │ ├── heartBeat.py │ ├── postBoomDeploy.py │ ├── preBoomDeploy.py │ └── transmitting.py ├── postBoomTime.txt └── saveTofiles.py ├── install.c ├── lastBase.txt ├── log.txt ├── mycron ├── protectionProticol └── fileProtection.py ├── requirements.txt ├── setNewTXWindow.c ├── startup.c ├── tests ├── README.md ├── __init__.py ├── misc │ └── boomDeployTest.py ├── postBoomTime.txt ├── quickTransmitTest.c ├── radio │ ├── 126bytefile.txt │ ├── 127bytefile.txt │ ├── 128bytefile.txt │ └── pipeMode.sh ├── resetFlightLogic.sh ├── testADC.py ├── testAccel.py ├── testAllDrivers.py ├── testAntennaDoor.py ├── testBackupAntennaDeployer.py ├── testBoomDeployer.py ├── testCamera.py ├── testCpuTemperature.py ├── testDummyFlightLogic.py ├── testEPSRaw.py ├── testEPSRead.py ├── testFlightLogic.py ├── testHeartbeat.py ├── testInterrupt.py ├── testMag.py ├── testMainFlightLogic.py ├── testMainFlightLogicNOTSub.py ├── testPrimaryAntennaDeploy.py ├── testRTC.py ├── testReceiving.py ├── testSPI.py ├── testSolarPanelTemp.py ├── testSolarPower.py ├── testSunSensor.py ├── testTransceiverConfig.py ├── testTransmissions.py ├── testUV.py ├── testWatchdogCommands.py └── unit_testing_example.py ├── upDateCode.c └── watchdog ├── Low-Power-master.zip ├── README.md ├── arduino_upload_notes.md ├── arduino_watchdog_v5 ├── README.md └── arduino_watchdog_v7.2.ino └── watchdog_v8.1 /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # pycharm 107 | .idea/ 108 | 109 | # vim 110 | *.swp 111 | 112 | # images 113 | *.jpg 114 | 115 | # Doxygen 116 | doxygen 117 | 118 | # Executables 119 | a.out 120 | 121 | 122 | ### GASPACS FILES ### 123 | 124 | # Flight logic data files 125 | flightLogic/data/* 126 | flightLogic/bootRecords 127 | flightLogic/backupBootRecords 128 | ~/flightLogicData/* 129 | ~/TXISRData/* 130 | flightLogic/TXISR/TXServiceCode/TXService.run 131 | install.exe 132 | upDataCode.exe 133 | flightConfig.exe 134 | flightConfigWifi.exe 135 | 136 | # TXISR Data Files 137 | TXISR/data/* 138 | # Picture Files 139 | Pictures/* 140 | -------------------------------------------------------------------------------- /AuthorsSignature.txt: -------------------------------------------------------------------------------- 1 | Team Lead/Desinger: Shawn Jones 2 | Project Coordinator: Jack Danos 3 | Contributors: Alex Allgrunn 4 | Carter Page 5 | Logan Freeman 6 | Josh Hessing 7 | Tom Christensen 8 | Evan Anderson 9 | Daniel Combs Jr. 10 | Ben Lewis 11 | Donna Metcalf 12 | -------------------------------------------------------------------------------- /Drivers/Accelerometer/Accelerometer.py: -------------------------------------------------------------------------------- 1 | #Make sure the following libraries are installed: 2 | # sudo pip3 install RPI.GPIO 3 | # sudo pip3 install adafruit-blinka 4 | # sudo pip3 install adafruit-circuitpython-lsm303-accel 5 | # For LSM303AGR: 6 | # sudo pip3 install adafruit-circuitpython-lis2mdl 7 | # For LSM303DLH: 8 | # sudo pip3 install adafruit-circuitpython-lsm303dlh-mag 9 | 10 | from Drivers.Driver import Driver 11 | import board 12 | import busio 13 | import adafruit_lsm303_accel 14 | 15 | """ 16 | Pulls data from the Accelerometer 17 | """ 18 | 19 | class Accelerometer(Driver): 20 | #Set up I2C link 21 | i2c = busio.I2C(board.SCL, board.SDA) 22 | 23 | def __init__(self): 24 | super().__init__("Accelerometer") 25 | 26 | def read(self): 27 | accel = adafruit_lsm303_accel.LSM303_Accel(self.i2c) 28 | return accel.acceleration 29 | -------------------------------------------------------------------------------- /Drivers/Accelerometer/README.md: -------------------------------------------------------------------------------- 1 | Accelerometer Driver 2 | -- 3 | Location: ../Drivers/Accelerometer/Accelerometer.py 4 | 5 | Functionality: 6 | The Accelerometer Driver sets up an I2C communication route to the Accelerometer. After this connection is made it can gather the acceleration data by calling: Accelerometer.Accelerometer.read() 7 | 8 | -------------------------------------------------------------------------------- /Drivers/Accelerometer/__init__.py: -------------------------------------------------------------------------------- 1 | from .Accelerometer import * 2 | -------------------------------------------------------------------------------- /Drivers/Driver.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | class Driver: 4 | """ 5 | Abstract class that defines a device driver. 6 | """ 7 | def __init__(self, name, delay=1, initial_delay=0): 8 | """ 9 | Initializes a Driver object. 10 | 1. self.name is a string identifying the object. 11 | 2. self.delay is an integer defining the refresh frequency of the driver. 12 | 3. self.initial_delay defines how long the driver should wait before beginning to refresh 13 | """ 14 | self.name = name 15 | self.delay = delay 16 | self.initial_delay = initial_delay 17 | 18 | def read(self): 19 | """ 20 | Abstract method that defines the behavior of the device driver. 21 | The child class implements this for communicating with specific hardware components. 22 | """ 23 | pass 24 | 25 | async def run(self, context, lock): 26 | """ 27 | Asynchronous method that controls how often driver reads values from the hardware component. 28 | Waits self.initial_delay time before beginning to read from the hardware. 29 | 1. context is the mission mode defined in the mission_modes module and passed to the driver by main. 30 | 2. lock controls asynchronous execution to avoid race conditions. 31 | """ 32 | if self.initial_delay > 0: 33 | await asyncio.sleep(self.initial_delay) 34 | self.initial_delay = 0 35 | 36 | while True: 37 | reading = self.read() 38 | async with lock: 39 | context[self.name] = reading 40 | await asyncio.sleep(self.delay) 41 | -------------------------------------------------------------------------------- /Drivers/Magnetometer/Magnetometer.py: -------------------------------------------------------------------------------- 1 | #Make sure the following libraries are installed: 2 | # sudo pip3 install RPI.GPIO 3 | # sudo pip3 install adafruit-blinka 4 | # sudo pip3 install adafruit-circuitpython-lsm303-accel 5 | # For LSM303AGR: 6 | # sudo pip3 install adafruit-circuitpython-lis2mdl 7 | # For LSM303DLH: 8 | # sudo pip3 install adafruit-circuitpython-lsm303dlh-mag 9 | """ 10 | Pulls data from the magnetometer 11 | """ 12 | 13 | from Drivers.Driver import Driver 14 | import board 15 | import busio 16 | #for LSM303AGR 17 | import adafruit_lis2mdl 18 | #for LSM303DLH 19 | #import adafruit_lsm303dlh_mag 20 | 21 | class Magnetometer(Driver): 22 | #Set up I2C link 23 | i2c = busio.I2C(board.SCL, board.SDA) 24 | 25 | def __init__(self): 26 | super().__init__("Magnetometer") 27 | 28 | def read(self) : 29 | #Set up link to magnetometer 30 | #for LSM303AGR 31 | mag = adafruit_lis2mdl.LIS2MDL(self.i2c) 32 | #for LSM303DLH 33 | #mag = adafruit_lsm303dlh_mag.LSM303DLH_Mag(self.i2c) 34 | return mag.magnetic 35 | -------------------------------------------------------------------------------- /Drivers/Magnetometer/README.md: -------------------------------------------------------------------------------- 1 | Magnetometer Driver: 2 | -- 3 | Location: ../Drivers/Magnetometer/Magnetometer.py 4 | 5 | Functionality: 6 | The Magnetometer Driver establishes an I2C connection with the Magnetometer. Once that connection is made the driver will then collect information concerning the direction, strength, or relative change of magnetic fields in relation to the satellite that have been gathered by the Magnetometer. To gain access to this information call Magnetometer.Magnetometer.read() 7 | -------------------------------------------------------------------------------- /Drivers/Magnetometer/__init__.py: -------------------------------------------------------------------------------- 1 | from .Magnetometer import * 2 | -------------------------------------------------------------------------------- /Drivers/README.md: -------------------------------------------------------------------------------- 1 | GASPACS Software: The Drivers 2 | === 3 | Opening Note: 4 | -- 5 | The purpose of the Drivers is to communicate between the Pi and the various sensors and components of the GASPACS mission. 6 | 7 | The following drivers return sensor data: 8 | - Accelerometer 9 | - Magnetometer 10 | - UV 11 | - adc 12 | - antennaDoor 13 | - cpuTemperature 14 | - rtc 15 | - solarPanelTemp 16 | - sunSensors 17 | 18 | The following drivers are designed to configure, command, or control components: 19 | - backupAntennaDeployer 20 | - boomDeployer 21 | 22 | The following drivers both configure components and return their values. 23 | - camera 24 | - transceiverConfig 25 | - eps 26 | 27 | All these drivers inherit from a single file called Driver.py. Their relationship with this file gives them the ability to run asynchronously as well as making the code more organized and easier to handle. For more documentation on each driver, there is a readme located in each Driver folder. 28 | 29 | -------------------------------------------------------------------------------- /Drivers/UV/README.md: -------------------------------------------------------------------------------- 1 | UV Driver: 2 | -- 3 | Location: ../Drivers/UV/UVDriver.py 4 | 5 | Functionality: 6 | The UV Driver inherits functionality from the ADC Driver, much like the Sun Sensor Driver. It reads from a specific channel through the SPi connection created in the ADC Driver, and returns a single value. To get the data returned by this driver call UVDriver.UVDriver.read() 7 | 8 | 9 | # UV Sensor Documentation 10 | ## Requirements 11 | The the value of uv power hitting the uv sensor and return the power in mW/cm^2 12 | ## Design 13 | > MCP3008 ADC 14 | >> * The adc driver for the MCP3008 ADC returns a list with values in volts 15 | >> * The uv sensor driver calls a method from the adc class, and gets the value in the uv sensor spot 16 | >> * Using the formula given by the graphs on the GUVA-S12D uv sensor datasheet( uvPower = voltage * 1000 / 485.9), plug in the value returned by the adc and return uvPower as a float 17 | 18 | > AD7998 ADC 19 | >> As of right now, the AD7998 ADC driver has not been written. 20 | Reading the datasheet tells us that the ADC will return a 12bit binary number over the I2C interface. 21 | As of now, assuming that the adc driver method call for the AD7998 adc returns a 12bit binary number, the design is as follows. 22 | >> * Using the VOLTAGE_STEP constant, which is defined as Vref_in / 4096 23 | >> * 4096 come from the maximum decimal value able to be represented from 12bit binary 24 | >> * The 12 bit binary number returned from the adc will be multiplied with the VOLTAGE_STEP constant to get the voltage 25 | >> * This voltage is then plugged into the previous equation stated, uvPower = voltage * 1000 / 485.9 26 | ## Implementation 27 | * import the adc class from the adc driver 28 | * create an adc object 29 | * call the read method specifying which register to return 30 | * take the return value and depending on which adc it is dealing with, follow the steps outlined above 31 | ## Testing 32 | > Testing with the MCP3008 ADC 33 | >> * Running the ADC code seperatley gives a list of register values 34 | >> * Running the uv sensor code returns the same value as the register in the 4th position 35 | 36 | > Testing with the AD7998 -------------------------------------------------------------------------------- /Drivers/UV/UVDriver.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | from Drivers.adc import ADC_Driver 3 | 4 | class UVDriver(Driver): 5 | """ 6 | This class calls the ADC driver and asks for data from the UV channel 7 | """ 8 | adc = ADC_Driver.ADC() 9 | uv_channel = 1 #The channel on the ADC that th UV sensor is connected to 10 | 11 | def __init__(self): 12 | super().__init__("UVDriver") 13 | 14 | def read(self): 15 | """ 16 | This function calls the read function of the ADC with the channel for the uv sensor 17 | """ 18 | return self.adc.read(self.uv_channel) 19 | -------------------------------------------------------------------------------- /Drivers/UV/__init__.py: -------------------------------------------------------------------------------- 1 | from .UVDriver import * 2 | -------------------------------------------------------------------------------- /Drivers/__init__.py: -------------------------------------------------------------------------------- 1 | from .Driver import * 2 | from .Magnetometer import * 3 | from .Accelerometer import * 4 | from .camera import * 5 | from .cpuTemperature import * 6 | from .adc import * 7 | from .UV import * 8 | from .rtc import * 9 | from .eps import * 10 | from .antennaDoor import * 11 | from .backupAntennaDeployer import * 12 | from .boomDeployer import * 13 | from .solarPanelTemp import * 14 | from .sunSensors import * 15 | from .transceiverConfig import * 16 | 17 | -------------------------------------------------------------------------------- /Drivers/adc/ADC_Driver.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | import spidev 3 | import RPi.GPIO as GPIO 4 | from time import sleep 5 | 6 | class ADC(Driver): 7 | """ 8 | This class interfaces with the ADC to read the voltage on a specified channel 9 | """ 10 | # Chip Select Pin. This is BOARD Pin 22, which is GPIO 25 11 | csPin = 25 12 | 13 | spi_ch = 0 14 | 15 | spi = spidev.SpiDev() 16 | # spi.open(bus, device) 17 | spi.open(0, spi_ch) 18 | # disable spidev's chip select. we need to manage this manually 19 | spi.no_cs = True 20 | spi.max_speed_hz = 1000 21 | # cs = chip select 22 | 23 | # Sleep after chip select delay 24 | csDelay = 0.01 25 | 26 | def __init__ (self): 27 | # these are the pins for miso, mosi, cs, clk. 28 | # these are where the board is hooked up 29 | super().__init__("ADC") 30 | 31 | 32 | GPIO.setmode(GPIO.BCM) 33 | GPIO.setup(self.csPin, GPIO.OUT, initial=GPIO.HIGH) 34 | 35 | print("Initializing ADC") 36 | 37 | def read(self, channel): 38 | """ 39 | Sends a read command with a specified channel and then returns the reply from the ADC 40 | """ 41 | # Start the read with both clock and chip select low 42 | GPIO.output(self.csPin, GPIO.LOW) 43 | sleep(self.csDelay) 44 | msg = (channel << 3) 45 | msg = [msg, 0b00000000] 46 | 47 | # print("Channel: ", channel) 48 | # print("Sent message: ", bin(msg[0]), bin(msg[1])) 49 | # print("Spi channel: ", self.spi) 50 | 51 | reply = self.spi.xfer2(msg) 52 | value = (reply[1] + (reply[0] * 256))*(3.3/4096) 53 | # set the clock and chip select to high to end message 54 | GPIO.output(self.csPin, GPIO.HIGH) 55 | sleep(self.csDelay) 56 | return value 57 | 58 | # convert the reply from 12 bits stored in two bytes to a voltage 59 | # 12 bit data: byte 0 [0 0 0 0 MSB d11 d10 d9] byte 1 [d8 d7 d6 d5 d4 d3 d2 LSB] 60 | # Each LSB represents 3.3/4096 volts 61 | 62 | def close(self): 63 | self.spi.close() 64 | -------------------------------------------------------------------------------- /Drivers/adc/ADC_Driver.py.save: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | import spidev 3 | import RPi.GPIO as GPIO 4 | 5 | 6 | class ADC(Driver): 7 | """ 8 | This class interfaces with the ADC to read the voltage on a specified channel 9 | """ 10 | csPin = 22 11 | 12 | spi_ch = 0 13 | spi = spidev.SpiDev() 14 | # spi.open(bus, device) 15 | spi.open(0, spi_ch) 16 | # disable spidev's chip select. we need to manage this manually 17 | spi.no_cs = True 18 | spi.max_speed_hz = 1000 19 | # cs = chip select 20 | 21 | def __init__ (self): 22 | # these are the pins for miso, mosi, cs, clk. 23 | # these are were the board is hooked up 24 | super().__init__("ADC") 25 | 26 | 27 | GPIO.setmode(GPIO.BOARD) 28 | GPIO.setup(self.csPin, GPIO.OUT, initial=GPIO.HIGH) 29 | 30 | def read(self, channel): 31 | """ 32 | Sends a read command with a specified channel and then returns the reply from the ADC 33 | """ 34 | # Start the read with both clock and chip select low 35 | GPIO.output(self.csPin, GPIO.LOW) 36 | 37 | # the following creates a message to send to the slave 38 | msg = (channel << 3) 39 | msg = [msg, 0b00000000] 40 | # the followin 41 | replyList = list() 42 | replySet = list() 43 | for i in range(0, 6): 44 | replyList[i] = self.spi.xfer2(msg) 45 | 46 | # set the clock and chip select to high to end message 47 | GPIO.output(self.csPin, GPIO.HIGH) 48 | 49 | # convert the reply from 12 bits stored in two bytes to a voltage 50 | # 12 bit data: byte 0 [0 0 0 0 MSB d11 d10 d9] byte 1 [d8 d7 d6 d5 d4 d3 d2 LSB] 51 | # Each LSB represents 3.3/4096 volts 52 | return(reply[1] + (reply[0] * 256))*(3.3/4096) 53 | -------------------------------------------------------------------------------- /Drivers/adc/README.md: -------------------------------------------------------------------------------- 1 | ADC Driver: 2 | -- 3 | Location: ../Drivers/adc/ADC_Driver.py 4 | 5 | Functionality: 6 | The ADC Driver sets up a SPi communication route with the ADC. Through the ADC it can then get data from our five Sun Sensors and one UV sensor. After this connection is made one can gather data from these sensors by calling: ADC_Driver.ADC.read(channel). 7 | (see Sun Sensor Driver and UV Driver) 8 | -------------------------------------------------------------------------------- /Drivers/adc/__init__.py: -------------------------------------------------------------------------------- 1 | from .ADC_Driver import * 2 | -------------------------------------------------------------------------------- /Drivers/antennaDoor/AntennaDoor.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | import smbus 3 | from time import sleep 4 | 5 | class AntennaDoor(Driver): 6 | 7 | def __init__(self): 8 | super().__init__("AntennaDoor") #Calls parent constructor 9 | """ 10 | Sets up I2C bus for communication 11 | """ 12 | self.DEVICE_BUS = 1 13 | self.DEVICE_ADDR = 0x33 14 | self.bus = smbus.SMBus(self.DEVICE_BUS) 15 | self.doorStatus = -1 16 | sleep(1) 17 | 18 | def readDoorStatus(self): 19 | """ 20 | Reads the I2C response from the antenna. 21 | The first 4 bits of the first byte represent the 4 antenna doors. 0 is not deployed, 1 is deployed. 22 | Uses bitwise "or" to check if all 4 antenna doors are deployed. If yes, set deployed True. If any 23 | doors are undeployed, set deployed False. 24 | """ 25 | # This command returns one byte from the antenna. Check the antenna manual for an explanation of the bytes. 26 | self.doorStatus = self.bus.read_byte(self.DEVICE_ADDR) 27 | doorBits = self.doorStatus 28 | print("Decimal value from antenna: ", doorBits) 29 | deployed = False 30 | # decimal representation of 00001111 31 | bitmask = 15 32 | bitwise = (doorBits | bitmask) 33 | print("Bitwise result: ", bitwise) 34 | if(bitwise == 255): 35 | deployed = True 36 | else : 37 | deployed = False 38 | print("deployed", deployed) 39 | return deployed 40 | 41 | #this is the command to deploy the anntenna 42 | def deployAntennaMain(self): 43 | try : 44 | deployed = self.readDoorStatus() 45 | except : 46 | self.doorStatus = -1 47 | 48 | print("\t____Deploying the Antenna____") 49 | if(self.doorStatus == 0): 50 | try : 51 | self.bus.write_byte(self.DEVICE_ADDR,0x1F) 52 | except : 53 | print("Failed to run 1") 54 | elif (not deployed): 55 | try : 56 | self.bus.write_byte(self.DEVICE_ADDR,0x2F) 57 | except : 58 | print("Failed to run 2") 59 | print("\t____Deployed the Antenna____") 60 | -------------------------------------------------------------------------------- /Drivers/antennaDoor/README.md: -------------------------------------------------------------------------------- 1 | Antenna Door Driver: 2 | -- 3 | Location: ../Drivers/antennaDoor/AntennaDoor.py 4 | 5 | Functionality: 6 | The Antenna Door Driver sets up an I2C communication route with the Antenna Doors. After this connection is established the Driver is then able to determine whether the doors have or have not opened in order to confirm antenna deployment. The data collected may be acquired by calling: AntennaDoor.AntennaDoor.readDoorStatus() 7 | -------------------------------------------------------------------------------- /Drivers/antennaDoor/__init__.py: -------------------------------------------------------------------------------- 1 | from .AntennaDoor import * 2 | -------------------------------------------------------------------------------- /Drivers/backupAntennaDeployer/BackupAntennaDeployer.py: -------------------------------------------------------------------------------- 1 | from asyncio.tasks import wait 2 | from Drivers.Driver import Driver 3 | import asyncio 4 | import time 5 | import RPi.GPIO as GPIO 6 | 7 | class BackupAntennaDeployer(Driver): 8 | def __init__(self): 9 | """ 10 | Calls parent constructor, sets burn time, sets GPIO pins 11 | """ 12 | super().__init__("BackupAntennaDeployer") 13 | # Initial values 14 | self.burnTime = 10 15 | 16 | # Set up the GPIO pins for use 17 | GPIO.setmode(GPIO.BCM) 18 | 19 | # Setup GPIO pins 20 | # Primary Backup Pin: BOARD 33 which is GPIO 13 21 | self.primaryPin = 13 22 | # Secondary Backup Pin: BOARD 32 which is GPIO 12 23 | self.secondaryPin = 12 24 | GPIO.setup(self.primaryPin,GPIO.OUT, initial=GPIO.LOW) 25 | GPIO.setup(self.secondaryPin,GPIO.OUT, initial=GPIO.LOW) 26 | 27 | async def deployPrimary(self): 28 | """ 29 | Set primary deploy pin to high for a specified time, triggering the 30 | backup antenna burn. 31 | """ 32 | #Burn primary backup, then turn off and wait 33 | self.PWMPRIMARY = GPIO.PWM(self.primaryPin, 500) 34 | self.PWMPRIMARY.start(0) 35 | try: 36 | while True: 37 | 38 | for dc in range(0, 101, 5): 39 | self.PWMPRIMARY.ChangeDutyCycle(dc) 40 | time.sleep(0.05) 41 | await asyncio.sleep(self.burnTime) 42 | 43 | GPIO.output(self.primaryPin, GPIO.LOW) 44 | self.PWMPRIMARY.stop() 45 | 46 | break 47 | except: 48 | print("Failed to use primary antenna deploy") 49 | 50 | # GPIO.output(self.primaryPin, GPIO.HIGH) 51 | # time.sleep() 52 | # await asyncio.sleep(self.burnTime) 53 | # GPIO.output(self.primaryPin, GPIO.LOW) 54 | 55 | async def deploySecondary(self): 56 | """ 57 | Set secondary deploy pin to high for a specified time, triggering the 58 | backup antenna burn. 59 | """ 60 | 61 | self.PWMSECONDARY = GPIO.PWM(self.secondaryPin, 500) 62 | self.PWMSECONDARY.start(0) 63 | try: 64 | while True: 65 | 66 | for dc in range(0, 101, 5): 67 | self.PWMSECONDARY.ChangeDutyCycle(dc) 68 | time.sleep(0.05) 69 | await asyncio.sleep(self.burnTime) 70 | 71 | GPIO.output(self.secondaryPin, GPIO.LOW) 72 | self.PWMSECONDARY.stop() 73 | 74 | break 75 | except: 76 | print("Failed to use secondary antenna deploy") 77 | 78 | 79 | # GPIO.output(self.secondaryPin, GPIO.HIGH) 80 | # await asyncio.sleep(self.burnTime) 81 | # GPIO.output(self.secondaryPin, GPIO.LOW) 82 | 83 | def read(self): 84 | """ 85 | Left undefined as no data is collected by this component 86 | """ 87 | pass 88 | -------------------------------------------------------------------------------- /Drivers/backupAntennaDeployer/README.md: -------------------------------------------------------------------------------- 1 | Backup Antenna Deployment Driver: 2 | -- 3 | Location: ../Drivers/backupAntennaDeployerBackupAntennaDeployer.py 4 | 5 | Functionality: 6 | The Backup Antenna Deployment Driver sets up a connection to various pins. These pins are connected to capacitors that power the Antenna Doors. Through this connection the software will set the capacitors to turn on and open the Antenna Doors. This code will be used as a fail safe if the premade software to open the Antenna Doors is unsuccessful. The Antenna can be manually deployed by calling: BackupAntennaDeployer.BackupAntennaDeployer.deploy(). 7 | -------------------------------------------------------------------------------- /Drivers/backupAntennaDeployer/__init__.py: -------------------------------------------------------------------------------- 1 | from .BackupAntennaDeployer import * 2 | -------------------------------------------------------------------------------- /Drivers/boomDeployer/BoomDeployer.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | from Drivers import EPS 3 | import asyncio 4 | import smbus 5 | import RPi.GPIO as GPIO 6 | import time 7 | 8 | class BoomDeployer(Driver): 9 | def __init__(self): 10 | """ 11 | Calls parent constructor, Defines initial burn time, time to wait in between burns, 12 | and how many times to burn before giving up. Sets up the GPIO pin for use by the actuate method. 13 | """ 14 | #super().__init__("BoomDeployer") 15 | # Initial values 16 | self.burnTimeWC1 = 10 17 | self.burnTimeWC2 = 10 18 | self.waitTime = 10 19 | self.numTimes = 3 20 | #The duty cycles will specify the maximum duty cycle for the PWM for each wire burn 21 | self.dutyCycle1 = 15 22 | self.dutyCycle2 = 15 23 | 24 | # Set up the GPIO pins for use 25 | GPIO.setmode(GPIO.BCM) 26 | 27 | # First Wirecutter 28 | # BOARD 38 is GPIO 20 29 | self.wireCutter1_high1 = 20 30 | # BOARD 36 is GPIO 16 31 | self.wireCutter1_high2 = 16 32 | # BOARD 7 is GPIO 4 33 | self.wireCutter1_low1 = 4 34 | 35 | # Set up Wirecutter 1 pins 36 | GPIO.setup(self.wireCutter1_high1, GPIO.OUT, initial=GPIO.LOW) 37 | GPIO.setup(self.wireCutter1_high2,GPIO.OUT, initial=GPIO.LOW) 38 | GPIO.setup(self.wireCutter1_low1,GPIO.OUT, initial=GPIO.HIGH) 39 | self.PWM1 = GPIO.PWM(self.wireCutter1_high1, 500) 40 | 41 | #Second Wirecutter 42 | # BOARD 37 is GPIO 26 43 | self.wireCutter2_high1 = 26 44 | # BOARD 35 is GPIO 19 45 | self.wireCutter2_high2 = 19 46 | # BOARD 29 is GPIO 5 47 | self.wireCutter2_low1 = 5 48 | 49 | # Set up Wirecutter 2 pins 50 | GPIO.setup(self.wireCutter2_high1, GPIO.OUT, initial=GPIO.LOW) 51 | GPIO.setup(self.wireCutter2_high2,GPIO.OUT, initial=GPIO.LOW) 52 | GPIO.setup(self.wireCutter2_low1,GPIO.OUT, initial=GPIO.HIGH) 53 | self.PWM2 = GPIO.PWM(self.wireCutter2_high1, 500) 54 | 55 | #used to turn on the eps bus 56 | self.Bus = EPS() 57 | 58 | async def deploy(self): 59 | """ 60 | Loop a specified number of times, setting the correct GPIO pins to HIGH/LOW to start/stop 61 | the burn. Wait and then repeat with the other wirecutter mechanism. 62 | Note: PWM is used on only one channel. 63 | """ 64 | #this is turn onthe raw out put on the bus, this is incase it turns off 65 | try: 66 | self.Bus.enableRaw() 67 | except: 68 | pass 69 | 70 | for num in range(0, self.numTimes): 71 | #Turn on Wire Cutter 1 72 | #GPIO.output(self.wireCutter1_high1, GPIO.HIGH) 73 | GPIO.output(self.wireCutter1_high2, GPIO.HIGH) 74 | GPIO.output(self.wireCutter1_low1, GPIO.LOW) 75 | self.PWM1.start(0) 76 | 77 | for dc in range(0, self.dutyCycle1, 5): 78 | self.PWM1.ChangeDutyCycle(dc) 79 | time.sleep(0.05) 80 | 81 | #Burn for set number of seconds 82 | await asyncio.sleep(self.burnTimeWC1) 83 | self.PWM1.stop() 84 | 85 | #Turn off Wire Cutter 1 86 | GPIO.output(self.wireCutter1_high1, GPIO.LOW) 87 | GPIO.output(self.wireCutter1_high2, GPIO.LOW) 88 | GPIO.output(self.wireCutter1_low1, GPIO.HIGH) 89 | #Wait 90 | await asyncio.sleep(self.waitTime) 91 | 92 | #Turn on Wire Cutter 2 93 | #GPIO.output(self.wireCutter2_high1, GPIO.HIGH) 94 | GPIO.output(self.wireCutter2_high2, GPIO.HIGH) 95 | GPIO.output(self.wireCutter2_low1, GPIO.LOW) 96 | self.PWM2.start(0) 97 | 98 | for dc in range(0, self.dutyCycle2, 5): 99 | self.PWM2.ChangeDutyCycle(dc) 100 | time.sleep(0.05) 101 | 102 | #Burn for set number of seconds 103 | await asyncio.sleep(self.burnTimeWC2) 104 | self.PWM2.stop() 105 | 106 | #Turn off Wire Cutter 2 107 | GPIO.output(self.wireCutter2_high1, GPIO.LOW) 108 | GPIO.output(self.wireCutter2_high2, GPIO.LOW) 109 | GPIO.output(self.wireCutter2_low1, GPIO.HIGH) 110 | #Wait 111 | await asyncio.sleep(self.waitTime) 112 | 113 | print('Loop executed once') 114 | 115 | def read(self): 116 | """ 117 | Left undefined as no data is collected by this component 118 | """ 119 | pass 120 | -------------------------------------------------------------------------------- /Drivers/boomDeployer/README.md: -------------------------------------------------------------------------------- 1 | Boom Deployer Driver: 2 | -- 3 | Location: ../Drivers/boomDeployer/BoomDeployer.py 4 | 5 | Functionality: 6 | Our AeroBoom is stored in a container held shut by wire. The Boom Deployer Driver will set up a connection to various pins. These pins will be connected to both the wire cutters. After this connection is made the Driver will tell the first wire cutter to turn on for three seconds and then turn off. It will do this twice. Since there is no way for us to tell if the first wire cutter was successful we will then turn on the second wire cutter and run it in the same manner. This ensures that the AeroBoom gets deployed. The AeroBoom gets deployed by calling: boomDeployer.BoomDeployer.deploy() 7 | -------------------------------------------------------------------------------- /Drivers/boomDeployer/__init__.py: -------------------------------------------------------------------------------- 1 | from .BoomDeployer import * 2 | -------------------------------------------------------------------------------- /Drivers/camera/.Camera.py.swo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallSatGasTeam/CubeWorks/d615cb27b897289bb3a5305b91c62256718685a1/Drivers/camera/.Camera.py.swo -------------------------------------------------------------------------------- /Drivers/camera/Camera.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | from time import sleep 3 | from picamera import PiCamera 4 | from os import listdir 5 | #from os.path import expanduser 6 | from os import makedirs 7 | from os import system 8 | from pathlib import Path 9 | import subprocess 10 | 11 | class Camera(Driver): 12 | def __init__(self): 13 | """ 14 | Takes a picture 15 | """ 16 | #super().__init__("Camera") 17 | 18 | self.highRes = (3280, 2464) 19 | self.lowRes = (640, 480) 20 | #self.pictureDirectoryPath = expanduser('~/Pictures') 21 | #self.pictureDirectoryPath = str(Path(__file__).parent / "../../Pictures") 22 | self.pictureDirectoryPath = "/home/pi/flightLogicData/Pictures" 23 | self.pictureNumber = 0 24 | try: 25 | self.__cam = PiCamera() 26 | print("made cam object") 27 | except: 28 | print("failed to make cam object") 29 | self.__cam = None 30 | 31 | def read(self): 32 | pass 33 | 34 | def takePicture(self): 35 | """ 36 | Takes the picture 37 | """ 38 | try : 39 | #the way to count folders in directory is len(os.listdir(path of directory to count in)) 40 | #you have to import OS 41 | #This also counts files in the total, but with the file structure we came up with this shouldn't be a problem 42 | makedirs(self.pictureDirectoryPath, exist_ok=True) 43 | self.pictureNumber = len(listdir(self.pictureDirectoryPath)) 44 | print("Picture number:", self.pictureNumber) 45 | #count number of folders in directory, add 1 for current pic 46 | 47 | self.__cam.resolution = self.lowRes 48 | print("Resolution:", self.__cam.resolution) 49 | sleep(2) 50 | makedirs(self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/LowRes", exist_ok=True) 51 | print("Made LowDir") 52 | self.__cam.capture(self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/LowRes/LowResOriginal"+str(self.pictureNumber)+".jpg") 53 | print("Captured Low") 54 | self.__cam.resolution = self.highRes 55 | print("Res:", self.__cam.resolution) 56 | makedirs(self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/HighRes", exist_ok=True) 57 | print("Made HighDir") 58 | self.__cam.capture(self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/HighRes/HighResOriginal"+str(self.pictureNumber)+".jpg") 59 | print("Took pictures") 60 | return self.pictureNumber 61 | except: 62 | #return neg 1 if no picture was taken 63 | print("Failed to take a picture") 64 | return -1 65 | 66 | def compressLowResToFiles(self, pictureNumber): 67 | """ 68 | Compresses the Low Res to files. Compresses with SSDV, converts from Hex to ASCII with xxd, splits into 128 byte files. 69 | """ 70 | self.pictureNumber = pictureNumber 71 | #Set up paths for low res picture and creates the packets directory 72 | lowResOriginalPath = self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/LowRes/LowResOriginal"+str(self.pictureNumber)+".jpg" 73 | lowResSSDVPath = self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/LowRes/LowResOriginal"+str(self.pictureNumber)+".bin" 74 | print("before compress") 75 | ssdv_lowRes_picture = system('sudo /home/pi/ssdv/ssdv -e -c N7GAS -i ' + str(pictureNumber) + " " + str(lowResOriginalPath) + ' ' + str(lowResSSDVPath)) 76 | print("After compress") 77 | def compressHighResToFiles(self,pictureNumber): 78 | """ 79 | Compresses the Low Res to files. Compresses with SSDV, converts from Hex to ASCII with xxd, splits into 128 byte files. 80 | """ 81 | self.pictureNumber = int(pictureNumber) 82 | 83 | #Set up paths for high res picture and creates the packets directory 84 | highResOriginalPath = self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/HighRes/HighResOriginal"+str(self.pictureNumber)+".jpg" 85 | highResSSDVPath = self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/HighRes/HighResOriginal"+str(self.pictureNumber)+".bin" 86 | 87 | ssdv_highRes_picture = system('sudo /home/pi/ssdv/ssdv -e -c N7GAS -i ' + str(pictureNumber + 100) + " " + str(highResOriginalPath) + ' ' + str(highResSSDVPath)) 88 | -------------------------------------------------------------------------------- /Drivers/camera/README.md: -------------------------------------------------------------------------------- 1 | Camera Driver: 2 | -- 3 | Location: ../Drivers/camera/Camera.py 4 | 5 | Functionality: 6 | The Camera Driver will take a photo and store the photo in a specific file with a very specific file path. This Driver will also give us the ability to compress the file into two different resolutions. In order to take a picture call Camera.Camera.takePicture(). In order to store the photo in either of the two resolutions call Camera.Camera.compressLowResToFiles(pictureNumber) 7 | Camera.Camera.compressHighResToFiles(pictureNumber) 8 | -------------------------------------------------------------------------------- /Drivers/camera/__init__.py: -------------------------------------------------------------------------------- 1 | from .Camera import * 2 | -------------------------------------------------------------------------------- /Drivers/cpuTemperature/CpuTemperature.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | from os import popen 3 | 4 | class CpuTemperature(Driver): 5 | def __init__(self): 6 | super().__init__(CpuTemperature) 7 | 8 | def read(self): 9 | temp = popen("vcgencmd measure_temp").readline() 10 | temp = float(temp.replace("temp="," ").replace("\'C"," ").replace("\n"," ")) 11 | return temp 12 | 13 | -------------------------------------------------------------------------------- /Drivers/cpuTemperature/README.md: -------------------------------------------------------------------------------- 1 | CPU Temperature Driver: 2 | -- 3 | Location: ../Drivers/cpuTemperature/CpuTemperature.py 4 | 5 | Functionality: 6 | The CPU Temperature Driver reads in the temperature of the CPU from the temperature sensor connected to the CPU. To collect the data gathered call CpuTemperature.CpuTemperature.read(). 7 | -------------------------------------------------------------------------------- /Drivers/cpuTemperature/__init__.py: -------------------------------------------------------------------------------- 1 | from .CpuTemperature import * 2 | -------------------------------------------------------------------------------- /Drivers/eps/EPS.py: -------------------------------------------------------------------------------- 1 | #EPS Driver, not tested against hardware yet 2 | from Drivers.Driver import Driver 3 | import smbus 4 | import RPi.GPIO as GPIO 5 | """ 6 | Pulls battery data and solar panel data from the EPS 7 | """ 8 | 9 | class EPS(Driver): 10 | #this sets up i2c commincation. 11 | def __init__(self): 12 | super().__init__("EPS") #Calls parent constructor 13 | 14 | #Setup I2C bus for communication 15 | self.DEVICE_BUS = 1 16 | self.DEVICE_ADDR = 0x18 17 | self.RegisterADR = 0x00 18 | self.bus = smbus.SMBus(self.DEVICE_BUS) 19 | 20 | def enableRaw(self): 21 | #This method enables RAW battery output from the EPS 22 | #The command is 3 bytes device address left-shifted by one bit, the command, and the state 23 | self.bus.write_byte_data(self.DEVICE_ADDR, 0x01, 0x03) 24 | 25 | def disableRaw(self): 26 | #This method disables RAW battery output from the EPS 27 | #The command is 3 bytes device address left-shifted by one bit, the command, and the state 28 | self.bus.write_byte_data(self.DEVICE_ADDR, 0x01, 0x02) 29 | 30 | def enableUHF(self): 31 | #This method sends the command to the EPS to enable UHF Transmission, and sets the corresponding GPIO Pin high on the Pi 32 | self.bus.write_byte_data(self.DEVICE_ADDR, 0x0E, 0x03) 33 | GPIO.setmode(GPIO.BOARD) 34 | GPIO.setup(18, GPIO.OUT, initial = GPIO.HIGH) 35 | GPIO.output(18, GPIO.HIGH) 36 | 37 | def read(self): 38 | #returns nothing since there are so many different things we could read, use other methods instead 39 | pass 40 | 41 | def startRead(self, command): 42 | value = self.bus.read_i2c_block_data(self.DEVICE_ADDR, command, 2) 43 | return (value[0] * 256) + value [1] 44 | 45 | #Getter calls read method, returns converted data - all values are converted 12 bits, even though 2 bytes returned 46 | def getMCUTemp(self): 47 | #super().__init__("ESP") <-- Shawn had this in here, I don't understand why it's here so I'm commenting it out and leaving it out of other ones 48 | temp = self.startRead(18) 49 | temp = ((temp *0.0006103516) - 0.986)/0.00355 50 | return temp #done with multiple lines because of complicated conversion 51 | def getCell1Temp(self): 52 | return self.startRead(19)*0.00390625 #Reads data of specified type, sets up conversion factor to 'C 53 | 54 | def getCell2Temp(self): 55 | return self.startRead(20)*0.00390625 #Reads data of specified type, sets up conversion factor to 'C 56 | 57 | def getBusVoltage(self): 58 | return self.startRead(1) * 0.0023394775 #Reads data of specified type, sets up conversion factor to V 59 | 60 | def getBusCurrent(self): 61 | return self.startRead(2) * 0.0030517578 #Reads data of specified type, sets up conversion factor to A 62 | 63 | def getBCRVoltage(self): 64 | return self.startRead(3) * 0.0023394775 #Reads data of specified type, sets up conversion factor to V 65 | 66 | def getBCRCurrent(self): 67 | return self.startRead(4) * 0.0015258789 #Reads data of specified type, sets up conversion factor to A 68 | 69 | def get3V3Current(self): 70 | return self.startRead(14) * 0.0020345052 #Reads data of specified type, sets up conversion factor to A 71 | 72 | def get5VCurrent(self): 73 | return self.startRead(15) * 0.0020345052 #Reads data of specified type, sets up conversion factor to A 74 | 75 | def getSPXVoltage(self): 76 | return self.startRead(5) * 0.0024414063 #Reads data of specified type, sets up conversion factor to V 77 | 78 | def getSPXMinusCurrent(self): 79 | return self.startRead(6) * 0.0006103516 #Reads data of specified type, sets up conversion factor to A 80 | 81 | def getSPXPlusCurrent(self): 82 | return self.startRead(7) * 0.0006103516 #Reads data of specified type, sets up conversion factor to A 83 | 84 | def getSPYVoltage(self): 85 | return self.startRead(8) * 0.0024414063 #Reads data of specified type, sets up conversion factor to V 86 | 87 | def getSPYMinusCurrent(self): 88 | return self.startRead(9) * 0.0006103516 #Reads data of specified type, sets up conversion factor to A 89 | 90 | def getSPYPlusCurrent(self): 91 | return self.startRead(10) * 0.0006103516 #Reads data of specified type, sets up conversion factor to A 92 | 93 | def getSPZVoltage(self): 94 | return self.startRead(11) * 0.0024414063 #Reads data of specified type, sets up conversion factor to V 95 | 96 | def getSPZPlusCurrent(self): 97 | return self.startRead(13) * 0.0006103516 98 | -------------------------------------------------------------------------------- /Drivers/eps/EPS.py.save: -------------------------------------------------------------------------------- 1 | #EPS Driver, not tested against hardware yet 2 | from Drivers.Driver import Driver 3 | import smbus 4 | import RPi.GPIO as GPIO 5 | 6 | class EPS(Driver): 7 | #this sets up i2c commincation. 8 | def __init__(self): 9 | super().__init__("EPS") #Calls parent constructor 10 | 11 | #Setup I2C bus for communication 12 | self.DEVICE_BUS = 1 13 | self.DEVICE_ADDR = 0x18 14 | self.RegisterADR = 0x00 15 | self.bus = smbus.SMBus(self.DEVICE_BUS) 16 | 17 | def enableRaw(self): 18 | #This method enables RAW battery output from the EPS 19 | #The command is 3 bytes device address left-shifted by one bit, the command, and the state 20 | self.bus.write_byte_data(self.DEVICE_ADDR, 0x01, 0x03) 21 | 22 | def disableRaw(self): 23 | #This method disables RAW battery output from the EPS 24 | #The command is 3 bytes device address left-shifted by one bit, the command, and the state 25 | self.bus.write_byte_data(self.DEVICE_ADDR, 0x01, 0x02) 26 | 27 | def enableUHF(self): 28 | #This method sends the command to the EPS to enable UHF Transmission, and sets the corresponding GPIO Pin high on the Pi 29 | self.bus.write_byte_data(self.DEVICE_ADDR, 0x0E, 0x03) 30 | GPIO.setmode(GPIO.BOARD) 31 | GPIO.setup(18, GPIO.OUT, initial = GPIO.HIGH) 32 | GPIO.output(18, GPIO.HIGH) 33 | 34 | def read(self): 35 | #returns nothing since there are so many different things we could read, use other methods instead 36 | pass 37 | 38 | def startRead(self, command): 39 | value = self.bus.read_i2c_block_data(self.DEVICE_ADDR, command, 2) 40 | return (value[0] * 256) + value [1] 41 | 42 | #Getter calls read method, returns converted data - all values are converted 12 bits, even though 2 bytes returned 43 | def getMCUTemp(self): 44 | #super().__init__("ESP") <-- Shawn had this in here, I don't understand why it's here so I'm commenting it out and leaving it out of other ones 45 | temp = self.startRead(18) 46 | temp = ((temp *0.0006103516) - 0.986)/0.00355 47 | return temp #done with multiple lines because of complicated conversion 48 | def getCell1Temp(self): 49 | return self.startRead(19)*0.00390625 #Reads data of specified type, sets up conversion factor to 'C 50 | 51 | def getCell2Temp(self): 52 | return self.startRead(20)*0.00390625 #Reads data of specified type, sets up conversion factor to 'C 53 | 54 | def getBusVoltage(self): 55 | return self.startRead(1) * 0.0023394775 #Reads data of specified type, sets up conversion factor to V 56 | 57 | def getBusCurrent(self): 58 | return self.startRead(2) * 0.0030517578 #Reads data of specified type, sets up conversion factor to A 59 | 60 | def getBCRVoltage(self): 61 | return self.startRead(3) * 0.0023394775 #Reads data of specified type, sets up conversion factor to V 62 | 63 | def getBCRCurrent(self): 64 | return self.startRead(4) * 0.0015258789 #Reads data of specified type, sets up conversion factor to A 65 | 66 | def get3V3Current(self): 67 | return self.startRead(14) * 0.0020345052 #Reads data of specified type, sets up conversion factor to A 68 | 69 | def get5VCurrent(self): 70 | return self.startRead(15) * 0.0020345052 #Reads data of specified type, sets up conversion factor to A 71 | 72 | def getSPXVoltage(self): 73 | return self.startRead(5) * 0.0024414063 #Reads data of specified type, sets up conversion factor to V 74 | 75 | def getSPXMinusCurrent(self): 76 | return self.startRead(6) * 0.0006103516 #Reads data of specified type, sets up conversion factor to A 77 | 78 | def getSPXPlusCurrent(self): 79 | return self.startRead(7) * 0.0006103516 #Reads data of specified type, sets up conversion factor to A 80 | 81 | def getSPYVoltage(self): 82 | return self.startRead(8) * 0.0024414063 #Reads data of specified type, sets up conversion factor to V 83 | 84 | def getSPYMinusCurrent(self): 85 | return self.startRead(9) * 0.0006103516 #Reads data of specified type, sets up conversion factor to A 86 | 87 | def getSPYPlusCurrent(self): 88 | return self.startRead(10) * 0.0006103516 #Reads data of specified type, sets up conversion factor to A 89 | 90 | def getSPZVoltage(self): 91 | return self.startRead(11) * 0.0024414063 #Reads data of specified type, sets up conversion factor to V 92 | 93 | def getSPZPlusCurrent(self): 94 | return self.startRead(13) * 0.0006103516 95 | -------------------------------------------------------------------------------- /Drivers/eps/README.md: -------------------------------------------------------------------------------- 1 | EPS Driver: 2 | -- 3 | Location: ../Drivers/eps/EPS.py 4 | 5 | Functionality: 6 | The EPS Driver sets up an I2C communication line with the EPS. Once this connection is established the Driver gains access to many different forms of data collected by the EPS. These data forms are: 7 | 8 | Cell temperature 9 | 10 | MCU temperature 11 | 12 | Bus voltage 13 | 14 | Bus current 15 | 16 | BCR Voltage 17 | 18 | BCR current 19 | 20 | 3V3 current 21 | 22 | 5V current 23 | 24 | SPX voltage 25 | 26 | SPX minus current 27 | 28 | SPX plus current 29 | 30 | SPY voltage 31 | 32 | SPY minus current 33 | 34 | SPY plus current 35 | 36 | SPZ current 37 | -------------------------------------------------------------------------------- /Drivers/eps/__init__.py: -------------------------------------------------------------------------------- 1 | from .EPS import * 2 | -------------------------------------------------------------------------------- /Drivers/eps/documentation.txt: -------------------------------------------------------------------------------- 1 | #This is the code that handle the i2c communication with the esp 2 | #it ment to be abstract and it handles the sending and reciving commands for you. 3 | #It will just return the desired information 4 | 5 | -------------------------------------------------------------------------------- /Drivers/rtc/README.md: -------------------------------------------------------------------------------- 1 | RTC Driver: 2 | -- 3 | Location: ../Drivers/rtc/rtc_driver.py 4 | 5 | Functionality: 6 | The RTC Driver is perhaps the most simple driver. All it does is read the system clock and return the value found there.This value will be the time in milliseconds since the Unix Epoch To get that value call: rtc_driver.RTC.read(). 7 | -------------------------------------------------------------------------------- /Drivers/rtc/__init__.py: -------------------------------------------------------------------------------- 1 | from .rtc_driver import RTC 2 | -------------------------------------------------------------------------------- /Drivers/rtc/rtc_driver.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | from datetime import datetime 3 | 4 | class RTC(Driver): 5 | def __init__(self): 6 | """ 7 | Initializes a simple driver that only reads the system clock. 8 | """ 9 | super().__init__('rtc') 10 | 11 | 12 | def readSeconds(self): 13 | """ 14 | Returns the UTC time in miliseconds since the Unix epoch. 15 | The integer returned should be 64 bits in size and be on the order of 1500000000000. 16 | """ 17 | return int((datetime.utcnow() - datetime.utcfromtimestamp(0)).total_seconds()) 18 | 19 | def readMilliseconds(self): 20 | return int((datetime.utcnow() - datetime.utcfromtimestamp(0)).total_seconds() * 1000) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Drivers/solarPanelTemp/README.md: -------------------------------------------------------------------------------- 1 | Solar Panel Temperature Driver: 2 | -- 3 | Location: ../Drivers/solarPanelTemp/solarDriver.py 4 | 5 | Functionality: 6 | The Solar Panel Temperature Driver establishes an SPi connection through the ADC to the two Temperature Sensors connected to the solar panels. After the connection is established this driver can return the temperature of the solar panels. In order to gain access to this data call: solarDriver.TempSensor.read(). 7 | -------------------------------------------------------------------------------- /Drivers/solarPanelTemp/__init__.py: -------------------------------------------------------------------------------- 1 | from .solarDriver import * 2 | -------------------------------------------------------------------------------- /Drivers/solarPanelTemp/solarDriver.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | import spidev 3 | import RPi.GPIO as GPIO 4 | from time import sleep 5 | 6 | class TempSensor(Driver): 7 | """ 8 | This class returns the temperature sensor values for both Endurosat solar panels. 9 | """ 10 | 11 | GPIO.setmode(GPIO.BCM) 12 | 13 | spi0 = spidev.SpiDev() 14 | # open SPI to Temp sensor0 15 | spi0.open(0, 1) 16 | spi0.max_speed_hz = 10 17 | spi0.no_cs = True 18 | # BOARD 26 is GPIO 7 19 | spi0_cs = 7 20 | GPIO.setup(spi0_cs, GPIO.OUT, initial=GPIO.HIGH) 21 | 22 | spi1 = spidev.SpiDev() 23 | # open SPI to Temp sensor1 24 | spi1.open(0, 1) 25 | spi1.max_speed_hz = 10 26 | spi1.no_cs = True 27 | # BOARD 24 is GPIO 8 28 | spi1_cs = 8 29 | GPIO.setup(spi1_cs, GPIO.OUT, initial=GPIO.HIGH) 30 | 31 | def __init__ (self): 32 | #super().__init__("ADC") 33 | pass 34 | 35 | def read(self): 36 | """ 37 | Sends a read command with a specified channel and then returns the reply from the ADC 38 | """ 39 | GPIO.output(self.spi0_cs, GPIO.LOW) 40 | temp0_raw = self.spi0.readbytes(2) #We need just the first 13 bits, as the last three bits of the 16 bit(2 byte) word are not used 41 | GPIO.output(self.spi0_cs, GPIO.HIGH) 42 | #This function converts the two bytes to the temperature value. The data is stored as two bytes where T13 is the most significant bit 43 | #| T13 T12 T11 T10 T9 T8 T7 T6 | | T5 T4 T3 T2 T1 1 1 1 | 44 | temp0 = ((temp0_raw[0] * 32) + (temp0_raw[1] >> 3))* 0.0625 45 | 46 | GPIO.output(self.spi1_cs, GPIO.LOW) 47 | temp1_raw = self.spi1.readbytes(2) #We need just the first 13 bits, as the last three bits of the 16 bit(2 byte) word are not used 48 | GPIO.output(self.spi1_cs, GPIO.HIGH) 49 | #This function converts the two bytes to the temperature value. The data is stored as two bytes where T13 is the most significant bit 50 | #| T13 T12 T11 T10 T9 T8 T7 T6 | | T5 T4 T3 T2 T1 1 1 1 | 51 | temp1 = ((temp1_raw[0] *32) + (temp1_raw[1] >> 3))* 0.0625 52 | 53 | return [temp0, temp1] 54 | -------------------------------------------------------------------------------- /Drivers/sunSensors/README.md: -------------------------------------------------------------------------------- 1 | Sun Sensor Driver: 2 | -- 3 | Location: ../Drivers/sunSensors/sunSensorDriver.py 4 | 5 | Functionality: 6 | The Sun Sensor Driver uses the ADC Drivers already established SPi connection. (See ADC Driver) Using polymorphism this driver reads in from multiple channels of communication via the ADC’s read() function. These channels are connected to the five Sun Sensors. The data collected from these sensors can be accessed by calling: sunSensorDriver.SunSensor.read() 7 | -------------------------------------------------------------------------------- /Drivers/sunSensors/__init__.py: -------------------------------------------------------------------------------- 1 | from .sunSensorDriver import * 2 | -------------------------------------------------------------------------------- /Drivers/sunSensors/sunSensorDriver.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | from Drivers.adc import ADC_Driver 3 | from time import sleep 4 | 5 | 6 | class sunSensor(Driver): 7 | """ 8 | This class calls the ADC driver and asks for data from the UV channel 9 | """ 10 | adc = ADC_Driver.ADC() 11 | adcChannel = [5, 4, 0, 2, 3] 12 | voltageList = [] 13 | 14 | def __init__(self): 15 | super().__init__("Sun Sensor") 16 | print("Initializing sun sensor") 17 | 18 | def read(self): 19 | """ 20 | This function calls the read function of the ADC for each channel a sun sensor has and return a list of the voltages 21 | """ 22 | self.voltageList = [] 23 | for i in range(0, 5): 24 | #self.voltageList.append(self.adc.read(self.adcChannel[i])) 25 | 26 | value = self.adc.read(self.adcChannel[i]) 27 | #print(value) 28 | self.voltageList.append(value) 29 | #sleep(.1) 30 | return self.voltageList 31 | 32 | def close(self): 33 | self.adc.close() 34 | -------------------------------------------------------------------------------- /Drivers/transceiverConfig/README.md: -------------------------------------------------------------------------------- 1 | Transceiver Configuration Driver: 2 | -- 3 | Location: ../Drivers/tranceiverConfig/TranceiverConfig.py 4 | 5 | Functionality: 6 | The Transceiver Configuration Driver does not handle the transmissions. It does, however, turn on and off the beacon, turn on low power mode, and read the internal temperature sensor. To access these different capabilities see the following commands: 7 | Turn off the beacon: TranceiverConfig.TranceiverConfig.setBeaconOff() 8 | Turn on the beacon: TranceiverConfig.TranceiverConfig.setBeaconOn() 9 | Set the Transceiver to low power mode: TranceiverConfig.TranceiverConfig.setLowPowerMode() 10 | Read in the internal temperature of the transceiver: TranceiverConfig.TranceiverConfig.read() 11 | -------------------------------------------------------------------------------- /Drivers/transceiverConfig/TransceiverConfig.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | import serial 3 | 4 | class TransceiverConfig(Driver): 5 | def __init__(self): 6 | """ 7 | The purpose of this driver is to modify the configuration of the Endurosat UHF Transceiver. 8 | It does not have anything to do with sending packets over the radio. 9 | We need the ability to turn on and off the beacon, turn on low power mode, and read the internal temp sensor. 10 | See Page 25 of the Endurosat UHF Transceiver Type II Manual Rev 1.8 document for the SCW bit description. 11 | 12 | ***Note: ALL ES+ commands need to be changed to reflect the address of the transceiver - currently set to 22*** 13 | ***Potentially need to add CRC32 checksum functionality*** 14 | """ 15 | super().__init__("TransceiverConfig") 16 | 17 | def writeData(self, input): 18 | """ 19 | Writes the input to the transceiver over UART 20 | """ 21 | ser = serial.Serial('/dev/serial0', 115200) 22 | data = input #Set data to the character 'a', 0x61 or 01100001 23 | ser.write(data) #Send the data 24 | #response = ser.read(128) 25 | ser.close() 26 | #return response 27 | 28 | def setBeaconOn(self): 29 | """ 30 | Turns on the morse beacon. Leaves all values at default except beacon. 31 | Binary SCW: 11001101000001 32 | Hex SCW: 3341 33 | """ 34 | self.writeData(b'ES+W22003341\r') 35 | 36 | def setBeaconOff(self): 37 | """ 38 | Turns off the morse beacon. All values are back to default. 39 | Binary SCW: 11001100000001 40 | Hex SCW: 3301 41 | """ 42 | self.writeData(b'ES+W22003301\r') 43 | 44 | def setLowPowerMode(self): 45 | """ 46 | Turns on Low Power Mode. Note: Any ESTTC command can be used to bring the transceiver out of low power mode 47 | """ 48 | self.writeData(b'ES+W22F4\r') 49 | 50 | def read(self): 51 | """ 52 | Returns the temperature from the transceiver internal temp sensor 53 | """ 54 | temp = self.writeData(b'ES+R220A\r') 55 | return temp 56 | -------------------------------------------------------------------------------- /Drivers/transceiverConfig/__init__.py: -------------------------------------------------------------------------------- 1 | from .TransceiverConfig import * 2 | -------------------------------------------------------------------------------- /DummyDrivers/Accelerometer/Accelerometer.py: -------------------------------------------------------------------------------- 1 | #Make sure the following libraries are installed: 2 | # sudo pip3 install RPI.GPIO 3 | # sudo pip3 install adafruit-blinka 4 | # sudo pip3 install adafruit-circuitpython-lsm303-accel 5 | # For LSM303AGR: 6 | # sudo pip3 install adafruit-circuitpython-lis2mdl 7 | # For LSM303DLH: 8 | # sudo pip3 install adafruit-circuitpython-lsm303dlh-mag 9 | 10 | from Drivers.Driver import Driver 11 | import board 12 | import busio 13 | import adafruit_lsm303_accel 14 | 15 | class Accelerometer(Driver): 16 | # Set up I2C link 17 | # i2c = busio.I2C(board.SCL, board.SDA) 18 | 19 | def __init__(self): 20 | super().__init__("Accelerometer") 21 | 22 | def read(self): 23 | return 9.6,0.3,0.3 24 | -------------------------------------------------------------------------------- /DummyDrivers/Accelerometer/README.md: -------------------------------------------------------------------------------- 1 | Accelerometer Driver 2 | -- 3 | Location: ../Drivers/Accelerometer/Accelerometer.py 4 | 5 | Functionality: 6 | The Accelerometer Driver sets up an I2C communication route to the Accelerometer. After this connection is made it can gather the acceleration data by calling: Accelerometer.Accelerometer.read() 7 | 8 | -------------------------------------------------------------------------------- /DummyDrivers/Accelerometer/__init__.py: -------------------------------------------------------------------------------- 1 | from .Accelerometer import * 2 | -------------------------------------------------------------------------------- /DummyDrivers/Driver.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | class Driver: 4 | """ 5 | Abstract class that defines a device driver. 6 | """ 7 | def __init__(self, name, delay=1, initial_delay=0): 8 | """ 9 | Initializes a Driver object. 10 | 1. self.name is a string identifying the object. 11 | 2. self.delay is an integer defining the refresh frequency of the driver. 12 | 3. self.initial_delay defines how long the driver should wait before beginning to refresh 13 | """ 14 | self.name = name 15 | self.delay = delay 16 | self.initial_delay = initial_delay 17 | 18 | def read(self): 19 | """ 20 | Abstract method that defines the behavior of the device driver. 21 | The child class implements this for communicating with specific hardware components. 22 | """ 23 | pass 24 | 25 | async def run(self, context, lock): 26 | """ 27 | Asynchronous method that controls how often driver reads values from the hardware component. 28 | Waits self.initial_delay time before beginning to read from the hardware. 29 | 1. context is the mission mode defined in the mission_modes module and passed to the driver by main. 30 | 2. lock controls asynchronous execution to avoid race conditions. 31 | """ 32 | if self.initial_delay > 0: 33 | await asyncio.sleep(self.initial_delay) 34 | self.initial_delay = 0 35 | 36 | while True: 37 | reading = self.read() 38 | async with lock: 39 | context[self.name] = reading 40 | await asyncio.sleep(self.delay) 41 | -------------------------------------------------------------------------------- /DummyDrivers/Magnetometer/Magnetometer.py: -------------------------------------------------------------------------------- 1 | #Make sure the following libraries are installed: 2 | # sudo pip3 install RPI.GPIO 3 | # sudo pip3 install adafruit-blinka 4 | # sudo pip3 install adafruit-circuitpython-lsm303-accel 5 | # For LSM303AGR: 6 | # sudo pip3 install adafruit-circuitpython-lis2mdl 7 | # For LSM303DLH: 8 | # sudo pip3 install adafruit-circuitpython-lsm303dlh-mag 9 | 10 | from Drivers.Driver import Driver 11 | import board 12 | import busio 13 | #for LSM303AGR 14 | import adafruit_lis2mdl 15 | #for LSM303DLH 16 | #import adafruit_lsm303dlh_mag 17 | 18 | class Magnetometer(Driver): 19 | #Set up I2C link 20 | #i2c = busio.I2C(board.SCL, board.SDA) 21 | def __init__(self): 22 | super().__init__("Magnetometer") 23 | 24 | def read(self) : 25 | #Set up link to magnetometer 26 | #for LSM303AGR 27 | #mag = adafruit_lis2mdl.LIS2MDL(self.i2c) 28 | #for LSM303DLH 29 | #mag = adafruit_lsm303dlh_mag.LSM303DLH_Mag(self.i2c) 30 | return 40.3,40.5,46.312 31 | -------------------------------------------------------------------------------- /DummyDrivers/Magnetometer/README.md: -------------------------------------------------------------------------------- 1 | Magnetometer Driver: 2 | -- 3 | Location: ../Drivers/Magnetometer/Magnetometer.py 4 | 5 | Functionality: 6 | The Magnetometer Driver establishes an I2C connection with the Magnetometer. Once that connection is made the driver will then collect information concerning the direction, strength, or relative change of magnetic fields in relation to the satellite that have been gathered by the Magnetometer. To gain access to this information call Magnetometer.Magnetometer.read() 7 | -------------------------------------------------------------------------------- /DummyDrivers/Magnetometer/__init__.py: -------------------------------------------------------------------------------- 1 | from .Magnetometer import * 2 | -------------------------------------------------------------------------------- /DummyDrivers/README.md: -------------------------------------------------------------------------------- 1 | GASPACS Software: The Drivers 2 | === 3 | Opening Note: 4 | -- 5 | The purpose of the Drivers is to communicate between the Pi and the various sensors and components of the GASPACS mission. 6 | 7 | The following drivers return sensor data: 8 | - Accelerometer 9 | - Magnetometer 10 | - UV 11 | - adc 12 | - antennaDoor 13 | - cpuTemperature 14 | - rtc 15 | - solarPanelTemp 16 | - sunSensors 17 | 18 | The following drivers are designed to configure, command, or control components: 19 | - backupAntennaDeployer 20 | - boomDeployer 21 | 22 | The following drivers both configure components and return their values. 23 | - camera 24 | - transceiverConfig 25 | - eps 26 | 27 | All these drivers inherit from a single file called Driver.py. Their relationship with this file gives them the ability to run asynchronously as well as making the code more organized and easier to handle. For more documentation on each driver, there is a readme located in each Driver folder. 28 | 29 | -------------------------------------------------------------------------------- /DummyDrivers/UV/README.md: -------------------------------------------------------------------------------- 1 | UV Driver: 2 | -- 3 | Location: ../Drivers/UV/UVDriver.py 4 | 5 | Functionality: 6 | The UV Driver inherits functionality from the ADC Driver, much like the Sun Sensor Driver. It reads from a specific channel through the SPi connection created in the ADC Driver, and returns a single value. To get the data returned by this driver call UVDriver.UVDriver.read() 7 | 8 | 9 | # UV Sensor Documentation 10 | ## Requirements 11 | The the value of uv power hitting the uv sensor and return the power in mW/cm^2 12 | ## Design 13 | > MCP3008 ADC 14 | >> * The adc driver for the MCP3008 ADC returns a list with values in volts 15 | >> * The uv sensor driver calls a method from the adc class, and gets the value in the uv sensor spot 16 | >> * Using the formula given by the graphs on the GUVA-S12D uv sensor datasheet( uvPower = voltage * 1000 / 485.9), plug in the value returned by the adc and return uvPower as a float 17 | 18 | > AD7998 ADC 19 | >> As of right now, the AD7998 ADC driver has not been written. 20 | Reading the datasheet tells us that the ADC will return a 12bit binary number over the I2C interface. 21 | As of now, assuming that the adc driver method call for the AD7998 adc returns a 12bit binary number, the design is as follows. 22 | >> * Using the VOLTAGE_STEP constant, which is defined as Vref_in / 4096 23 | >> * 4096 come from the maximum decimal value able to be represented from 12bit binary 24 | >> * The 12 bit binary number returned from the adc will be multiplied with the VOLTAGE_STEP constant to get the voltage 25 | >> * This voltage is then plugged into the previous equation stated, uvPower = voltage * 1000 / 485.9 26 | ## Implementation 27 | * import the adc class from the adc driver 28 | * create an adc object 29 | * call the read method specifying which register to return 30 | * take the return value and depending on which adc it is dealing with, follow the steps outlined above 31 | ## Testing 32 | > Testing with the MCP3008 ADC 33 | >> * Running the ADC code seperatley gives a list of register values 34 | >> * Running the uv sensor code returns the same value as the register in the 4th position 35 | 36 | > Testing with the AD7998 -------------------------------------------------------------------------------- /DummyDrivers/UV/UVDriver.py: -------------------------------------------------------------------------------- 1 | from DummyDrivers.Driver import Driver 2 | from DummyDrivers.adc import ADC_Driver 3 | 4 | class UVDriver(Driver): 5 | """ 6 | This class calls the ADC driver and asks for data from the UV channel 7 | """ 8 | #adc = ADC_Driver.ADC() 9 | #uv_channel = 1 #The channel on the ADC that th UV sensor is connected to 10 | 11 | def __init__(self): 12 | super().__init__("UVDriver") 13 | 14 | def read(self): 15 | """ 16 | This function calls the read function of the ADC with the channel for the uv sensor 17 | """ 18 | return 16.234 19 | -------------------------------------------------------------------------------- /DummyDrivers/UV/__init__.py: -------------------------------------------------------------------------------- 1 | from .UVDriver import * 2 | -------------------------------------------------------------------------------- /DummyDrivers/__init__.py: -------------------------------------------------------------------------------- 1 | from .Driver import * 2 | from .Magnetometer import * 3 | from .Accelerometer import * 4 | from .camera import * 5 | from .cpuTemperature import * 6 | from .adc import * 7 | from .UV import * 8 | from .rtc import * 9 | from .eps import * 10 | from .antennaDoor import * 11 | from .backupAntennaDeployer import * 12 | from .boomDeployer import * 13 | from .solarPanelTemp import * 14 | from .sunSensors import * 15 | from .transceiverConfig import * 16 | 17 | -------------------------------------------------------------------------------- /DummyDrivers/adc/ADC_Driver.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | import spidev 3 | import RPi.GPIO as GPIO 4 | 5 | 6 | class ADC(Driver): 7 | """ 8 | This class interfaces with the ADC to read the voltage on a specified channel 9 | """ 10 | #csPin = 22 11 | 12 | #spi_ch = 0 13 | #spi = spidev.SpiDev() 14 | # spi.open(bus, device) 15 | #spi.open(0, spi_ch) 16 | # disable spidev's chip select. we need to manage this manually 17 | #spi.no_cs = True 18 | # cs = chip select 19 | 20 | def __init__ (self): 21 | # these are the pins for miso, mosi, cs, clk. 22 | # these are were the board is hooked up 23 | super().__init__("ADC") 24 | #GPIO.setmode(GPIO.BOARD) 25 | #GPIO.setup(self.csPin, GPIO.OUT, initial=GPIO.HIGH) 26 | 27 | def read(self, channel): 28 | """ 29 | Sends a read command with a specified channel and then returns the reply from the ADC 30 | """ 31 | # Start the read with both clock and chip select low 32 | #GPIO.output(self.csPin, GPIO.LOW) 33 | 34 | # the following creates a message to send to the slave 35 | #msg = (channel << 3) 36 | #msg = [msg, 0b00000000] 37 | # the followin 38 | #reply = self.spi.xfer2(msg) 39 | 40 | # set the clock and chip select to high to end message 41 | #GPIO.output(self.csPin, GPIO.HIGH) 42 | 43 | # convert the reply from 12 bits stored in two bytes to a voltage 44 | # 12 bit data: byte 0 [0 0 0 0 MSB d11 d10 d9] byte 1 [d8 d7 d6 d5 d4 d3 d2 LSB] 45 | # Each LSB represents 3.3/4096 volts 46 | return 'Dummy ADC used' 47 | -------------------------------------------------------------------------------- /DummyDrivers/adc/README.md: -------------------------------------------------------------------------------- 1 | ADC Driver: 2 | -- 3 | Location: ../Drivers/adc/ADC_Driver.py 4 | 5 | Functionality: 6 | The ADC Driver sets up a SPi communication route with the ADC. Through the ADC it can then get data from our five Sun Sensors and one UV sensor. After this connection is made one can gather data from these sensors by calling: ADC_Driver.ADC.read(channel). 7 | (see Sun Sensor Driver and UV Driver) 8 | -------------------------------------------------------------------------------- /DummyDrivers/adc/__init__.py: -------------------------------------------------------------------------------- 1 | from .ADC_Driver import * 2 | -------------------------------------------------------------------------------- /DummyDrivers/antennaDoor/AntennaDoor.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | import smbus 3 | 4 | class AntennaDoor(Driver): 5 | 6 | def __init__(self): 7 | super().__init__("AntennaDoor") #Calls parent constructor 8 | 9 | #Setup I2C bus for communication 10 | #self.DEVICE_BUS = 1 11 | #self.DEVICE_ADDR = 0x33 12 | #self.RegisterADR = 0x00 # I do not know what this means or what it should be 13 | #self.bus = smbus.SMBus(self.DEVICE_BUS) 14 | 15 | def readDoorStatus(self): 16 | #returns the status of all 4 antenna doors 17 | #doorStatus = self.bus.read_i2c_block_data(self.DEVICE_ADDR, self.RegisterADR, 1) 18 | return (0,0,0,0) 19 | 20 | -------------------------------------------------------------------------------- /DummyDrivers/antennaDoor/README.md: -------------------------------------------------------------------------------- 1 | Antenna Door Driver: 2 | -- 3 | Location: ../Drivers/antennaDoor/AntennaDoor.py 4 | 5 | Functionality: 6 | The Antenna Door Driver sets up an I2C communication route with the Antenna Doors. After this connection is established the Driver is then able to determine whether the doors have or have not opened in order to confirm antenna deployment. The data collected may be acquired by calling: AntennaDoor.AntennaDoor.readDoorStatus() 7 | -------------------------------------------------------------------------------- /DummyDrivers/antennaDoor/__init__.py: -------------------------------------------------------------------------------- 1 | from .AntennaDoor import * 2 | -------------------------------------------------------------------------------- /DummyDrivers/backupAntennaDeployer/BackupAntennaDeployer.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | import asyncio 3 | import RPi.GPIO as GPIO 4 | 5 | class BackupAntennaDeployer(Driver): 6 | def __init__(self): 7 | """ 8 | Calls parent constructor, sets burn time, sets GPIO pins 9 | """ 10 | super().__init__("BackupAntennaDeployer") 11 | # Initial values 12 | self.burnTime = 10 13 | 14 | # Set up the GPIO pins for use 15 | #GPIO.setmode(GPIO.BOARD) 16 | 17 | #Setup GPIO pins 18 | #self.primaryPin = 33 19 | #self.secondaryPin = 32 20 | #GPIO.setup(self.primaryPin,GPIO.OUT, initial=GPIO.LOW) 21 | #GPIO.setup(self.secondaryPin,GPIO.OUT, initial=GPIO.LOW) 22 | 23 | async def deployPrimary(self): 24 | """ 25 | Set primary deploy pin to high for a specified time, triggering the 26 | backup antenna burn. 27 | """ 28 | #Burn primary backup, then turn off and wait 29 | #GPIO.output(self.primaryPin, GPIO.HIGH) 30 | #await asyncio.sleep(self.burnTime) 31 | #GPIO.output(self.primaryPin, GPIO.LOW) 32 | print("Deploy Primary") 33 | 34 | async def deploySecondary(self): 35 | """ 36 | Set secondary deploy pin to high for a specified time, triggering the 37 | backup antenna burn. 38 | """ 39 | #GPIO.output(self.secondaryPin, GPIO.HIGH) 40 | #await asyncio.sleep(self.burnTime) 41 | #GPIO.output(self.secondaryPin, GPIO.LOW) 42 | print("Deploy secondary") 43 | 44 | def read(self): 45 | """ 46 | Left undefined as no data is collected by this component 47 | """ 48 | pass 49 | -------------------------------------------------------------------------------- /DummyDrivers/backupAntennaDeployer/README.md: -------------------------------------------------------------------------------- 1 | Backup Antenna Deployment Driver: 2 | -- 3 | Location: ../Drivers/backupAntennaDeployerBackupAntennaDeployer.py 4 | 5 | Functionality: 6 | The Backup Antenna Deployment Driver sets up a connection to various pins. These pins are connected to capacitors that power the Antenna Doors. Through this connection the software will set the capacitors to turn on and open the Antenna Doors. This code will be used as a fail safe if the premade software to open the Antenna Doors is unsuccessful. The Antenna can be manually deployed by calling: BackupAntennaDeployer.BackupAntennaDeployer.deploy(). 7 | -------------------------------------------------------------------------------- /DummyDrivers/backupAntennaDeployer/__init__.py: -------------------------------------------------------------------------------- 1 | from .BackupAntennaDeployer import * 2 | -------------------------------------------------------------------------------- /DummyDrivers/boomDeployer/BoomDeployer.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | from time import sleep 3 | import RPi.GPIO as GPIO 4 | 5 | class BoomDeployer(Driver): 6 | def __init__(self): 7 | """ 8 | Calls parent constructor, Defines initial burn time, time to wait in between burns, 9 | and how many times to burn before giving up. Sets up the GPIO pin for use by the actuate method. 10 | """ 11 | super().__init__("BoomDeployer") 12 | """ 13 | # Initial values 14 | self.burnTime = 1 15 | self.waitTime = 3 16 | self.numTimes = 3 17 | 18 | # Set up the GPIO pins for use 19 | #GPIO.setmode(GPIO.BOARD) 20 | 21 | #First Wirecutter 22 | self.wireCutter1_high1 = 36 23 | self.wireCutter1_high2 = 38 24 | self.wireCutter1_low1 = 7 25 | GPIO.setup(self.wireCutter1_high1, GPIO.OUT, initial=GPIO.LOW) 26 | GPIO.setup(self.wireCutter1_high2,GPIO.OUT, initial=GPIO.LOW) 27 | GPIO.setup(self.wireCutter1_low1,GPIO.OUT, initial=GPIO.HIGH) 28 | 29 | #Second Wirecutter 30 | self.wireCutter2_high1 = 35 31 | self.wireCutter2_high2 = 37 32 | self.wireCutter2_low1 = 29 33 | GPIO.setup(self.wireCutter2_high1, GPIO.OUT, initial=GPIO.LOW) 34 | GPIO.setup(self.wireCutter2_high2,GPIO.OUT, initial=GPIO.LOW) 35 | GPIO.setup(self.wireCutter2_low1,GPIO.OUT, initial=GPIO.HIGH) 36 | """ 37 | 38 | async def deploy(self): 39 | """ 40 | Loop a specified number of times, setting the correct GPIO pins to HIGH/LOW to start/stop 41 | the burn. Wait and then repeat with the other wirecutter mechanism 42 | """ 43 | """ 44 | for num in range(0, self.numTimes): 45 | #Turn on Wire Cutter 1 46 | GPIO.output(self.wireCutter1_high1, GPIO.HIGH) 47 | GPIO.output(self.wireCutter1_high2, GPIO.HIGH) 48 | GPIO.output(self.wireCutter1_low1, GPIO.LOW) 49 | #Burn for set number of seconds 50 | sleep(self.burnTime) 51 | #Turn off Wire Cutter 1 52 | GPIO.output(self.wireCutter1_high1, GPIO.LOW) 53 | GPIO.output(self.wireCutter1_high2, GPIO.LOW) 54 | GPIO.output(self.wireCutter1_low1, GPIO.HIGH) 55 | #Wait 56 | sleep(self.waitTime) 57 | 58 | #Turn on Wire Cutter 2 59 | GPIO.output(self.wireCutter2_high1, GPIO.HIGH) 60 | GPIO.output(self.wireCutter2_high2, GPIO.HIGH) 61 | GPIO.output(self.wireCutter2_low1, GPIO.LOW) 62 | #Burn for set number of seconds 63 | sleep(self.burnTime) 64 | #Turn off Wire Cutter 2 65 | GPIO.output(self.wireCutter2_high1, GPIO.LOW) 66 | GPIO.output(self.wireCutter2_high2, GPIO.LOW) 67 | GPIO.output(self.wireCutter2_low1, GPIO.HIGH) 68 | #Wait 69 | sleep(self.waitTime) 70 | GPIO.cleanup() 71 | """ 72 | print("Triggering dummy deploy") 73 | def read(self): 74 | """ 75 | Left undefined as no data is collected by this component 76 | """ 77 | pass 78 | 79 | -------------------------------------------------------------------------------- /DummyDrivers/boomDeployer/README.md: -------------------------------------------------------------------------------- 1 | Boom Deployer Driver: 2 | -- 3 | Location: ../Drivers/boomDeployer/BoomDeployer.py 4 | 5 | Functionality: 6 | Our AeroBoom is stored in a container held shut by wire. The Boom Deployer Driver will set up a connection to various pins. These pins will be connected to both the wire cutters. After this connection is made the Driver will tell the first wire cutter to turn on for three seconds and then turn off. It will do this twice. Since there is no way for us to tell if the first wire cutter was successful we will then turn on the second wire cutter and run it in the same manner. This ensures that the AeroBoom gets deployed. The AeroBoom gets deployed by calling: boomDeployer.BoomDeployer.deploy() 7 | -------------------------------------------------------------------------------- /DummyDrivers/boomDeployer/__init__.py: -------------------------------------------------------------------------------- 1 | from .BoomDeployer import * 2 | -------------------------------------------------------------------------------- /DummyDrivers/camera/.Camera.py.swo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallSatGasTeam/CubeWorks/d615cb27b897289bb3a5305b91c62256718685a1/DummyDrivers/camera/.Camera.py.swo -------------------------------------------------------------------------------- /DummyDrivers/camera/Camera.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | from time import sleep 3 | from picamera import PiCamera 4 | from os import listdir 5 | from os.path import expanduser 6 | from os import makedirs 7 | import subprocess 8 | 9 | class Camera(Driver): 10 | def __init__(self): 11 | """ 12 | Takes a picture 13 | """ 14 | super().__init__("Camera") 15 | """ 16 | self.highRes = (3280, 2464) 17 | self.lowRes = (640, 480) 18 | self.pictureDirectoryPath = expanduser('~/Pictures') 19 | self.cam = None 20 | self.pictureNumber = 0 21 | """ 22 | 23 | def read(self): 24 | pass 25 | 26 | def takePicture(self): 27 | """ 28 | Takes the picture 29 | 30 | #the way to count folders in directory is len(os.listdir(path of directory to count in)) 31 | #you have to import OS 32 | #This also counts files in the total, but with the file structure we came up with this shouldn't be a problem 33 | try: 34 | self.pictureNumber = len(listdir(self.pictureDirectoryPath)) 35 | except FileNotFoundError: 36 | self.pictureNumber = 0 37 | #count number of folders in directory, add 1 for current pic 38 | 39 | self.cam = PiCamera() 40 | self.cam.resolution = self.lowRes 41 | sleep(2) 42 | makedirs(self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/LowRes", exist_ok=True) 43 | self.cam.capture(self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/LowRes/LowResOriginal"+str(self.pictureNumber)+".jpg") 44 | self.cam.resolution = self.highRes 45 | makedirs(self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/HighRes", exist_ok=True) 46 | self.cam.capture(self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/HighRes/HighResOriginal"+str(self.pictureNumber)+".jpg") 47 | """ 48 | print("Dummy taking picture") 49 | 50 | def compressLowResToFiles(self, pictureNumber): 51 | """ 52 | Compresses the Low Res to files. Compresses with SSDV, converts from Hex to ASCII with xxd, splits into 128 byte files. 53 | 54 | self.pictureNumber = pictureNumber 55 | #Set up paths for low res picture and creates the packets directory 56 | lowResOriginalPath = self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/LowRes/LowResOriginal"+str(self.pictureNumber)+".jpg" 57 | lowResSSDVPath = self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/LowRes/LowResOriginal"+str(self.pictureNumber)+".bin" 58 | lowResASCIIPath = self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/LowRes/LowResOriginal"+str(self.pictureNumber)+".txt" 59 | makedirs(self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/LowRes/Packets", exist_ok=True) 60 | lowResPacketPath = self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/LowRes/Packets/" 61 | 62 | 63 | ssdv_lowRes_picture = subprocess.run(["ssdv", "-e", lowResOriginalPath, lowResSSDVPath]) 64 | xxd_lowRes_picture = subprocess.run(["xxd", lowResSSDVPath, lowResASCIIPath]) 65 | split_lowRes_picture = subprocess.run(["split", "-b", "128", lowResASCIIPath, lowResPacketPath]) 66 | """ 67 | print("Dummy compress low res") 68 | 69 | def compressHighResToFiles(self,pictureNumber): 70 | """ 71 | Compresses the Low Res to files. Compresses with SSDV, converts from Hex to ASCII with xxd, splits into 128 byte files. 72 | 73 | self.pictureNumber = pictureNumber 74 | 75 | #Set up paths for high res picture and creates the packets directory 76 | highResOriginalPath = self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/HighRes/HighResOriginal"+str(self.pictureNumber)+".jpg" 77 | highResSSDVPath = self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/HighRes/HighResOriginal"+str(self.pictureNumber)+".bin" 78 | highResASCIIPath = self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/HighRes/HighResOriginal"+str(self.pictureNumber)+".txt" 79 | makedirs(self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/HighRes/Packets", exist_ok=True) 80 | highResPacketPath = self.pictureDirectoryPath+"/"+str(self.pictureNumber)+"/HighRes/Packets/" 81 | 82 | ssdv_highRes_picture = subprocess.run(["ssdv", "-e", highResOriginalPath, highResSSDVPath]) 83 | xxd_highRes_picture = subprocess.run(["xxd", highResSSDVPath, highResASCIIPath]) 84 | split_highRes_picture = subprocess.run(["split", "-b", "128", highResASCIIPath, highResPacketPath]) 85 | """ 86 | print("dummy compress high res") 87 | -------------------------------------------------------------------------------- /DummyDrivers/camera/README.md: -------------------------------------------------------------------------------- 1 | Camera Driver: 2 | -- 3 | Location: ../Drivers/camera/Camera.py 4 | 5 | Functionality: 6 | The Camera Driver will take a photo and store the photo in a specific file with a very specific file path. This Driver will also give us the ability to compress the file into two different resolutions. In order to take a picture call Camera.Camera.takePicture(). In order to store the photo in either of the two resolutions call Camera.Camera.compressLowResToFiles(pictureNumber) 7 | Camera.Camera.compressHighResToFiles(pictureNumber) 8 | -------------------------------------------------------------------------------- /DummyDrivers/camera/__init__.py: -------------------------------------------------------------------------------- 1 | from .Camera import * 2 | -------------------------------------------------------------------------------- /DummyDrivers/cpuTemperature/CpuTemperature.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | from os import popen 3 | 4 | class CpuTemperature(Driver): 5 | def __init__(self): 6 | super().__init__(CpuTemperature) 7 | 8 | def read(self): 9 | temp = popen("vcgencmd measure_temp").readline() 10 | temp = float(temp.replace("temp="," ").replace("\'C"," ").replace("\n"," ")) 11 | return temp 12 | 13 | -------------------------------------------------------------------------------- /DummyDrivers/cpuTemperature/README.md: -------------------------------------------------------------------------------- 1 | CPU Temperature Driver: 2 | -- 3 | Location: ../Drivers/cpuTemperature/CpuTemperature.py 4 | 5 | Functionality: 6 | The CPU Temperature Driver reads in the temperature of the CPU from the temperature sensor connected to the CPU. To collect the data gathered call CpuTemperature.CpuTemperature.read(). 7 | -------------------------------------------------------------------------------- /DummyDrivers/cpuTemperature/__init__.py: -------------------------------------------------------------------------------- 1 | from .CpuTemperature import * 2 | -------------------------------------------------------------------------------- /DummyDrivers/eps/EPS.py: -------------------------------------------------------------------------------- 1 | #EPS Driver, not tested against hardware yet 2 | from Drivers.Driver import Driver 3 | import smbus 4 | #circurt bus??? maybe use that 5 | 6 | class EPS(Driver): 7 | #this sets up i2c commincation. 8 | def __init__(self): 9 | super().__init__("EPS") #Calls parent constructor 10 | 11 | #Setup I2C bus for communication 12 | self.DEVICE_BUS = 1 13 | self.DEVICE_ADDR = 0x18 14 | self.RegisterADR = 0x00 15 | #self.bus = smbus.SMBus(self.DEVICE_BUS) 16 | 17 | def enableRaw(self): 18 | #This method enables RAW battery output from the EPS 19 | #The command is 3 bytes device address left-shifted by one bit, the command, and the state 20 | tempAddress = self.DEVICE_ADDR << 1 21 | #self.bus.write_byte_data(self.DEVICE_ADDR, self.RegisterADR, tempAddress) 22 | #self.bus.write_byte_data(self.DEVICE_ADDR, self.RegisterADR, 0x01) #Battery Raw BUS 23 | #self.bus.write_byte_data(self.DEVICE_ADDR, self.RegisterADR, 0x03) #Forced ON state 24 | 25 | def disableRaw(self): 26 | #This method disables RAW battery output from the EPS 27 | #The command is 3 bytes device address left-shifted by one bit, the command, and the state 28 | tempAddress = self.DEVICE_ADDR << 1 29 | #self.bus.write_byte_data(self.DEVICE_ADDR, self.RegisterADR, tempAddress) 30 | #self.bus.write_byte_data(self.DEVICE_ADDR, self.RegisterADR, 0x01) #Battery Raw BUS 31 | #self.bus.write_byte_data(self.DEVICE_ADDR, self.RegisterADR, 0x02) #Forced OFF state <-- Do we want to use Forced OFF/ON or Auto OFF/ON? 32 | 33 | def read(self): 34 | #returns nothing since there are so many different things we could read, use other methods instead 35 | pass 36 | 37 | def __startRead(command): 38 | #This method sends read commands to the EPS. Shawn wrote it, I assume it's correct --Logan 39 | #this code starts the command process, for read commands it is address bit shifted left by one, then the command, and then address bit 40 | #shifted right by one. 41 | #Note: you may have to wait for an acknowledge bit, but this is stander i2c proticol so I'm not sure if python does it automatically. 42 | tempAddress = self.DEVICE_ADDR << 1 43 | #self.bus.write_byte_data(self.DEVICE_ADDR, self.RegisterADR, tempAddress) 44 | #self.bus.write_byte_data(self.DEVICE_ADDR, self.RegisterADR, command) # do you need do convert the int to hex somehow? 45 | #tempAddress = self.DEVICE_ADDR >> 1 46 | #self.bus.write_byte_data(self.DEVICE_ADDR, self.RegisterADR, tempAddress) 47 | #the read command always returns two bytes. 48 | #return self.bus.read_i2c_block_data(self.DEVICE_ADDR, self.RegisterADR, 2) 49 | 50 | #Getter calls read method, returns converted data - all values are converted 12 bits, even though 2 bytes returned 51 | def getMCUTemp(self): 52 | #super().__init__("ESP") <-- Shawn had this in here, I don't understand why it's here so I'm commenting it out and leaving it out of other ones 53 | temp = 26 54 | temp = ((temp *0.0006103516) - 0.986)/0.00355 55 | return temp #done with multiple lines because of complicated conversion 56 | def getCell1Temp(self): 57 | return 11*0.00390625 #Reads data of specified type, sets up conversion factor to 'C 58 | 59 | def getCell2Temp(self): 60 | return 12*0.00390625 #Reads data of specified type, sets up conversion factor to 'C 61 | 62 | def getBusVoltage(self): 63 | return 13000 * 0.0023394775 #Reads data of specified type, sets up conversion factor to V 64 | 65 | def getBusCurrent(self): 66 | return 14 * 0.0030517578 #Reads data of specified type, sets up conversion factor to A 67 | 68 | def getBCRVoltage(self): 69 | return 15000 * 0.0023394775 #Reads data of specified type, sets up conversion factor to V 70 | 71 | def getBCRCurrent(self): 72 | return 16 * 0.0015258789 #Reads data of specified type, sets up conversion factor to A 73 | 74 | def get3V3Current(self): 75 | return 17 * 0.0020345052 #Reads data of specified type, sets up conversion factor to A 76 | 77 | def get5VCurrent(self): 78 | return 18 * 0.0020345052 #Reads data of specified type, sets up conversion factor to A 79 | 80 | def getSPXVoltage(self): 81 | return 21 * 0.0024414063 #Reads data of specified type, sets up conversion factor to V 82 | 83 | def getSPXMinusCurrent(self): 84 | return 22 * 0.0006103516 #Reads data of specified type, sets up conversion factor to A 85 | 86 | def getSPXPlusCurrent(self): 87 | return 23 * 0.0006103516 #Reads data of specified type, sets up conversion factor to A 88 | 89 | def getSPYVoltage(self): 90 | return 24 * 0.0024414063 #Reads data of specified type, sets up conversion factor to V 91 | 92 | def getSPYMinusCurrent(self): 93 | return 25 * 0.0006103516 #Reads data of specified type, sets up conversion factor to A 94 | 95 | def getSPYPlusCurrent(self): 96 | return 26 * 0.0006103516 #Reads data of specified type, sets up conversion factor to A 97 | 98 | def getSPZVoltage(self): 99 | return 27 * 0.0024414063 #Reads data of specified type, sets up conversion factor to V 100 | 101 | def getSPZPlusVoltage(self): 102 | return 28 * 0.0006103516 103 | -------------------------------------------------------------------------------- /DummyDrivers/eps/README.md: -------------------------------------------------------------------------------- 1 | EPS Driver: 2 | -- 3 | Location: ../Drivers/eps/EPS.py 4 | 5 | Functionality: 6 | The EPS Driver sets up an I2C communication line with the EPS. Once this connection is established the Driver gains access to many different forms of data collected by the EPS. These data forms are: 7 | 8 | Cell temperature 9 | 10 | MCU temperature 11 | 12 | Bus voltage 13 | 14 | Bus current 15 | 16 | BCR Voltage 17 | 18 | BCR current 19 | 20 | 3V3 current 21 | 22 | 5V current 23 | 24 | SPX voltage 25 | 26 | SPX minus current 27 | 28 | SPX plus current 29 | 30 | SPY voltage 31 | 32 | SPY minus current 33 | 34 | SPY plus current 35 | 36 | SPZ current 37 | -------------------------------------------------------------------------------- /DummyDrivers/eps/__init__.py: -------------------------------------------------------------------------------- 1 | from .EPS import * 2 | -------------------------------------------------------------------------------- /DummyDrivers/eps/documentation.txt: -------------------------------------------------------------------------------- 1 | #This is the code that handle the i2c communication with the esp 2 | #it ment to be abstract and it handles the sending and reciving commands for you. 3 | #It will just return the desired information 4 | 5 | -------------------------------------------------------------------------------- /DummyDrivers/rtc/README.md: -------------------------------------------------------------------------------- 1 | RTC Driver: 2 | -- 3 | Location: ../Drivers/rtc/rtc_driver.py 4 | 5 | Functionality: 6 | The RTC Driver is perhaps the most simple driver. All it does is read the system clock and return the value found there.This value will be the time in milliseconds since the Unix Epoch To get that value call: rtc_driver.RTC.read(). 7 | -------------------------------------------------------------------------------- /DummyDrivers/rtc/__init__.py: -------------------------------------------------------------------------------- 1 | from .rtc_driver import RTC 2 | -------------------------------------------------------------------------------- /DummyDrivers/rtc/rtc_driver.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | from datetime import datetime 3 | 4 | class RTC(Driver): 5 | def __init__(self): 6 | """ 7 | Initializes a simple driver that only reads the system clock. 8 | """ 9 | super().__init__('rtc') 10 | 11 | 12 | def readSeconds(self): 13 | """ 14 | Returns the UTC time in miliseconds since the Unix epoch. 15 | The integer returned should be 64 bits in size and be on the order of 1500000000000. 16 | """ 17 | return int((datetime.utcnow() - datetime.utcfromtimestamp(0)).total_seconds()) 18 | 19 | def readMilliseconds(self): 20 | return int((datetime.utcnow() - datetime.utcfromtimestamp(0)).total_seconds() * 1000) 21 | 22 | 23 | -------------------------------------------------------------------------------- /DummyDrivers/solarPanelTemp/README.md: -------------------------------------------------------------------------------- 1 | Solar Panel Temperature Driver: 2 | -- 3 | Location: ../Drivers/solarPanelTemp/solarDriver.py 4 | 5 | Functionality: 6 | The Solar Panel Temperature Driver establishes an SPi connection through the ADC to the two Temperature Sensors connected to the solar panels. After the connection is established this driver can return the temperature of the solar panels. In order to gain access to this data call: solarDriver.TempSensor.read(). 7 | -------------------------------------------------------------------------------- /DummyDrivers/solarPanelTemp/__init__.py: -------------------------------------------------------------------------------- 1 | from .solarDriver import * 2 | -------------------------------------------------------------------------------- /DummyDrivers/solarPanelTemp/solarDriver.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | import spidev 3 | import RPi.GPIO as GPIO 4 | 5 | 6 | class TempSensor(Driver): 7 | """ 8 | This class interfaces with the ADC to read a specified channel 9 | """ 10 | #spi0 = spidev.SpiDev() 11 | # open SPI to Temp sensor0 12 | #spi0.open(0, 0) 13 | #spi1 = spidev.SpiDev() 14 | # open SPI to Temp sensor1 15 | #spi1.open(0, 1) 16 | 17 | def __init__ (self): 18 | super().__init__("ADC") 19 | 20 | def read(self): 21 | """ 22 | Sends a read command with a specified channel and then returns the reply from the ADC 23 | """ 24 | #temp0_raw = self.spi0.readbytes(2) #We need just the first 13 bits, as the last three bits of the 16 bit(2 byte) word are not used 25 | 26 | #This function converts the two bytes to the temperature value. The data is stored as two bytes where T13 is the most significant bit 27 | #| T13 T12 T11 T10 T9 T8 T7 T6 | | T5 T4 T3 T2 T1 1 1 1 | 28 | #temp0 = ((temp0_raw[0] * 64) + (temp0_raw[1] >> 3))* 0.0625 29 | 30 | #temp1_raw = self.spi1.readbytes(2) #We need just the first 13 bits, as the last three bits of the 16 bit(2 byte) word are not used 31 | 32 | #This function converts the two bytes to the temperature value. The data is stored as two bytes where T13 is the most significant bit 33 | #| T13 T12 T11 T10 T9 T8 T7 T6 | | T5 T4 T3 T2 T1 1 1 1 | 34 | #temp1 = ((temp1_raw[0] * 64) + (temp1_raw[1] >> 3))* 0.0625 35 | 36 | return [61.2, 62.1] 37 | -------------------------------------------------------------------------------- /DummyDrivers/sunSensors/README.md: -------------------------------------------------------------------------------- 1 | Sun Sensor Driver: 2 | -- 3 | Location: ../Drivers/sunSensors/sunSensorDriver.py 4 | 5 | Functionality: 6 | The Sun Sensor Driver uses the ADC Drivers already established SPi connection. (See ADC Driver) Using polymorphism this driver reads in from multiple channels of communication via the ADC’s read() function. These channels are connected to the five Sun Sensors. The data collected from these sensors can be accessed by calling: sunSensorDriver.SunSensor.read() 7 | -------------------------------------------------------------------------------- /DummyDrivers/sunSensors/__init__.py: -------------------------------------------------------------------------------- 1 | from .sunSensorDriver import * 2 | -------------------------------------------------------------------------------- /DummyDrivers/sunSensors/sunSensorDriver.py: -------------------------------------------------------------------------------- 1 | from DummyDrivers.Driver import Driver 2 | from DummyDrivers.adc import ADC_Driver 3 | from datetime import datetime 4 | 5 | class sunSensor(Driver): 6 | """ 7 | This class calls the ADC driver and asks for data from the UV channel 8 | """ 9 | #adc = ADC_Driver.ADC() 10 | #adcChannel = [5, 4, 2, 3, 0] 11 | #voltageList = [] 12 | 13 | def __init__(self): 14 | # super().__init__("Sun Sensor") 15 | self.voltageList=[] * 5 16 | 17 | def read(self): 18 | # """ 19 | # This function calls the read function of the ADC for each channel a sun sensor has and return a list of the voltages 20 | # """ 21 | # for i in range(0, 5): 22 | # self.voltageList.append(51) 23 | 24 | # return self.voltageList 25 | 26 | v = [0, 0, 0, 0, 0] 27 | self.voltageList = [0, 0, 0, 0, 0] 28 | getTime = int((datetime.utcnow() - datetime.utcfromtimestamp(0)).total_seconds()) 29 | #Sun length is the amount of time the satellite will be in the sun 30 | sunLength = 1.75 31 | 32 | interval = sunLength*60 33 | switch = 20 34 | 35 | if ((getTime % interval) == 0) | ((getTime % interval) == switch): 36 | v[0] = 1.5 37 | v[4] = v[3] = v[2] = v[1] = v[0] 38 | elif (getTime % interval) > switch: 39 | v[4] = v[3] = v[2] = v[1] = v[0] = 3.3 40 | elif (getTime % interval) < switch: 41 | v[4] = v[3] = v[2] = v[1] = v[0] = 0.0 42 | 43 | self.voltageList = v 44 | 45 | print("Dummy sun sensor driver voltage list: ", self.voltageList, "Time, interval, switch: ", getTime%interval, interval, switch) 46 | 47 | return self.voltageList -------------------------------------------------------------------------------- /DummyDrivers/transceiverConfig/README.md: -------------------------------------------------------------------------------- 1 | Transceiver Configuration Driver: 2 | -- 3 | Location: ../Drivers/tranceiverConfig/TranceiverConfig.py 4 | 5 | Functionality: 6 | The Transceiver Configuration Driver does not handle the transmissions. It does, however, turn on and off the beacon, turn on low power mode, and read the internal temperature sensor. To access these different capabilities see the following commands: 7 | Turn off the beacon: TranceiverConfig.TranceiverConfig.setBeaconOff() 8 | Turn on the beacon: TranceiverConfig.TranceiverConfig.setBeaconOn() 9 | Set the Transceiver to low power mode: TranceiverConfig.TranceiverConfig.setLowPowerMode() 10 | Read in the internal temperature of the transceiver: TranceiverConfig.TranceiverConfig.read() 11 | -------------------------------------------------------------------------------- /DummyDrivers/transceiverConfig/TransceiverConfig.py: -------------------------------------------------------------------------------- 1 | from Drivers.Driver import Driver 2 | import serial 3 | 4 | class TransceiverConfig(Driver): 5 | def __init__(self): 6 | """ 7 | The purpose of this driver is to modify the configuration of the Endurosat UHF Transceiver. 8 | It does not have anything to do with sending packets over the radio. 9 | We need the ability to turn on and off the beacon, turn on low power mode, and read the internal temp sensor. 10 | See Page 25 of the Endurosat UHF Transceiver Type II Manual Rev 1.8 document for the SCW bit description. 11 | 12 | ***Note: ALL ES+ commands need to be changed to reflect the address of the transceiver - currently set to 23*** 13 | ***Potentially need to add CRC32 checksum functionality*** 14 | """ 15 | super().__init__("TransceiverConfig") 16 | 17 | def writeData(self, input): 18 | """ 19 | Writes the input to the transceiver over UART 20 | """ 21 | #ser = serial.Serial("/dev/ttyAMA0") #Open named port 22 | #ser.baudrate = 115200 #Set baud rate to 9600 23 | #ser.timeout = 1 24 | #data = input #Set data to the character 'a', 0x61 or 01100001 25 | #ser.write(data.encode()) #Send the data 26 | #response = ser.read(128) 27 | #ser.close() 28 | #return response 29 | pass 30 | 31 | def setBeaconOn(self): 32 | """ 33 | Turns on the morse beacon. Leaves all values at default except beacon. 34 | Binary SCW: 11001101000001 35 | Hex SCW: 3341 36 | """ 37 | #self.writeData("ES+W23003341") 38 | pass 39 | 40 | def setBeaconOff(self): 41 | """ 42 | Turns off the morse beacon. All values are back to default. 43 | Binary SCW: 11001100000001 44 | Hex SCW: 3301 45 | """ 46 | #self.writeData("ES+W23003301") 47 | pass 48 | 49 | def setLowPowerMode(self): 50 | """ 51 | Turns on Low Power Mode. Note: Any ESTTC command can be used to bring the transceiver out of low power mode 52 | """ 53 | #self.writeData("ES+W23F4") 54 | pass 55 | 56 | def read(self): 57 | """ 58 | Returns the temperature from the transceiver internal temp sensor 59 | """ 60 | #temp = self.writeData("ES+R230A") 61 | #return temp 62 | pass 63 | 64 | -------------------------------------------------------------------------------- /DummyDrivers/transceiverConfig/__init__.py: -------------------------------------------------------------------------------- 1 | from .TransceiverConfig import * 2 | -------------------------------------------------------------------------------- /TXISR/InformationDocs/TXServiceCode User Guide.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallSatGasTeam/CubeWorks/d615cb27b897289bb3a5305b91c62256718685a1/TXISR/InformationDocs/TXServiceCode User Guide.docx -------------------------------------------------------------------------------- /TXISR/InformationDocs/TXServiceCodeTest: -------------------------------------------------------------------------------- 1 | Test Procuder for the txisr (c code) 2 | 3 | 4 | 1: run the program and make sure the the program runs with no erros 5 | 2: run program and check the out put files make sure the files have been up dated with the correct time. 6 | 3: connect the program to the radio and make sure it sends the correct data 7 | 4: let program finish the trigger the isr again to see if it reconfigures the pins correctly 8 | -------------------------------------------------------------------------------- /TXISR/InformationDocs/TXServiceCode_User_Guide.txt: -------------------------------------------------------------------------------- 1 | TXServiceCode User Guide 2 | 3 | How to use: The TXServiceCode is designed to be able to simplify the transmission process to the radio receiver. It takes a single file (txFile.txt), this file contains the information need to transmit, and the information that the user desires to transmit. The file follows the following format. 4 | First line: The transition window in milliseconds. EX 900000 (15 minutes) 5 | All subsequent lines: 000000001 (time stamp, 10 chars) : (divider flag) Data. EX 000000001: 123,123,123 6 | 7 | Notes: 8 | 1. DO NOT put an extra new line at the end of the file. 9 | 2. The divider is not transmitted 10 | 3. If the divider happens to be contained in the data it will still be transmitted. 11 | 4. There are certain data types that do not transmit the time stamp and the divider. 12 | 13 | To call the TXService code it is required that you navigate to the piTXISR folder, the full path is CubeWorks/TXISR/piTXISR. Once in the folder the code is called by calling the they binary executable, these files most commonly end in .out or .run, although this is not strictly required. Usually lunix will highlight this files in a different color. The data type also need to follow the excitable name. The format is as follows ./ EX ./TXSerice.run 1 14 | 15 | Notes: 16 | 1. There are 5 data types. (0 - 4) 17 | 2. If the code is passed data type 3 it does not transmit the time stamp. This is meant to be used for pictures. If multiply data types need to have this behavior this may easy be changed in the TXSerciveCode.c If the number would like to be changed that can also be done easaly by changing the corresponding #define in the first lines of the code. I 18 | 19 | Workings of the TXSeriveCode.c: As a forestated the object of this code is to simplify the transmission process. Therefore the process is as follows, 20 |  Step one: The serial port is opened, and the files are verified as being good files, if either of these steps fail the code terminates with an abnormal termination flag. 21 |  Step two, the code waits exactly 5 seconds for the transmission window. 22 | • Note: This means that the code should be called exactly 5 seconds before the transmission window. 23 | • Note: The code uses delta t this means that the code does not need to know what the system time is. It also is not important that the system time is correct so long as delta t is still correct. 24 |  Step three: the radio is put into pip mode 25 |  Step four the transmission 26 | • The code takes in one line of the txFile.txt at a times. It reads the time stamp and then transmit the data. 27 | • The code then saves the time stamp to the flagsFile.txt. 28 | • The code waits 120 milliseconds. 29 | • This process is repeated until all the data is sent or the transmission window expires. 30 | Notes: 31 | 1. The code will NOT watch the packet size, if one line of data is more then 128, it will still transmit the whole line. 32 | 2. The code saves the time stand after every transmission. If the code should fail the last send transmission will always be saved. 33 | 34 | Compiling the code: GCC is used to compile the TXServiceCode. There are two files that should be created each time the code is compiled, 1 a.out and 2 TXService.run. To create this files the process is as follows. 35 | i) Navigate to the piTXISR folder. 36 | ii) Enter gcc TXServiceCode.c (This code is for the testUART.py) 37 | iii) Enter gcc TXServiceCode.c -o TXService.run 38 | iv) Enter cd .. 39 | v) Enter cp piTXISR/TXService.run TXServiceCode 40 | Camera files: Camera transmission files have only one difference from normal transmission files, this is that the time stamp is replaced with a line number. EX 1: (camera data). The line number is important because should a transmission fail we will know where to resume transmissions on the next window. 41 | 42 | Shawn Jones 43 | GASPACS Software Team Lead 44 | 45 | 46 | -------------------------------------------------------------------------------- /TXISR/README.md: -------------------------------------------------------------------------------- 1 | GASPACS Software: The TXISR 2 | == 3 | Interrupt: 4 | -- 5 | Location: ../TXISR/interrupt.py 6 | 7 | Functionality: The code that comprises the Interrupt does one thing. When a transmission comes from a ground station it will interrupt what is happening on the Raspberry Pi so that we can handle the transmissions to and from the ground. After the interrupt, the software will then go to the TX/RX stage. 8 | 9 | TX/RX (Transmit and Receive): 10 | -- 11 | Location: ../TXISR/rxHandling.py 12 | 13 | Functionality: The TX/RX stage does the main bulk of the work needed to be done in order to transmit a message back to the ground. During this stage the software will: 14 | Decode the message received from the ground. 15 | Determine the type of packets or data requested. 16 | Get the queried data from the database, packetize data and write the data to a file. 17 | Determine from the received message when the transmission window will start. 18 | Determine how long the transmission window will last. 19 | Then wait till five seconds before the scheduled window for transmission. 20 | 21 | NOTES: 22 | -- 23 | The data will be data collected by the Drivers (see GASPACS Software I: The Drivers). 24 | For more information on transmissions from the ground see GASPACS Flight Logic Plan Outline Appendix C Tables 5, 6 and 7. 25 | For information on transmissions to the ground see GASPACS Flight Logic Plan Outline Appendix B Tables 1, 2, 3 and 4. 26 | 27 | -------------------------------------------------------------------------------- /TXISR/TXServiceCode/TXService.run: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallSatGasTeam/CubeWorks/d615cb27b897289bb3a5305b91c62256718685a1/TXISR/TXServiceCode/TXService.run -------------------------------------------------------------------------------- /TXISR/TXServiceCode/debug.h: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef DEBUG 3 | #define PRINT_DEBUG(n) printf("Value of " #n ": %d\n", n); 4 | #define PRINT_DEBUG_f(n) printf("Value of " #n ": %f\n", n); 5 | #define PRINT_DEBUG_c(n) printf("Value of " #n ": %c\n", n); 6 | #define DEBUG_REST(n) printf(#n" Has been rest\n"); 7 | #define DEBUG_P(n) printf(#n"\n"); 8 | #define PRINT_DEBUG_CHAR(n) putchar(n); 9 | #define PRINT_TIME(n) printf("%jd milliseconds\n", (intmax_t)n); 10 | #define PRINT_LONG(n) printf(#n" %ld\n", n); 11 | #define PRINT_HEX(n) printf("hex value: %X\n", n); 12 | #else 13 | #define PRINT_DEBUG(n) 14 | #define PRINT_DEBUG_f(n) 15 | #define PRINT_DEBUG_c(n) 16 | #define DEBUG_REST(n) 17 | #define DEBUG_P(n) 18 | #define PRINT_DEBUG_CHAR(n) 19 | #define PRINT_TIME(n) 20 | #define PRINT_LONG(n) 21 | #define PRINT_HEX(n) 22 | #endif -------------------------------------------------------------------------------- /TXISR/TXServiceCode/readMe: -------------------------------------------------------------------------------- 1 | #All the code that is not in a folder, are source code! They are files that are used to compile and run the system code 2 | #Hurrah! I found the file that had literally nothing important in it so that I can push and not worry about killing everything! -------------------------------------------------------------------------------- /TXISR/__init__.py: -------------------------------------------------------------------------------- 1 | #Nothing 2 | 3 | -------------------------------------------------------------------------------- /TXISR/pythonInterrupt.py: -------------------------------------------------------------------------------- 1 | import serial 2 | import asyncio 3 | import sys 4 | sys.path.append('../') 5 | from TXISR.packetProcessing import packetProcessing 6 | from protectionProticol.fileProtection import FileReset 7 | from time import sleep 8 | 9 | 10 | fileChecker = FileReset() 11 | 12 | """ 13 | This file sets up the interrupt process. Every five seconds, the buffer of the serial port at /dev/serial0 is read. 14 | Content is split up into AX.25 Packets, and Command Packets. The data is passed to Jack's packetProcessing.py methods. 15 | TODO: Implement AX.25 digipeating, probably in packetProcessing.py 16 | To defray the possibility of half a packet being in the buffer, any half-packets are stored and evaluated the next time around 17 | """ 18 | 19 | async def interrupt(transmitObject, packetObj): 20 | packet = packetObj 21 | fileChecker.fullReset() 22 | try: 23 | serialport = serial.Serial('/dev/serial0', 115200) #Open serial port. Currently /dev/serial0, might change to the PL011 port for flight article 24 | except Exception as e: 25 | print("Failed to open serialport. Exception:", repr(e)) 26 | serialport = None 27 | leftovers = '' #Stores any half-packets for evaluation the next loop 28 | # leftoversEmpty = True 29 | gaspacsHex = str(b'GASPACS'.hex()) 30 | while True: 31 | if transmitObject.isRunning(): 32 | await asyncio.sleep(5) 33 | continue 34 | try: 35 | if serialport == None: 36 | print("Reopening serial port") 37 | serialport = serial.Serial('/dev/serial0', 115200) #Open serial port. Currently /dev/serial0, might change to the PL011 port for flight article 38 | print("Python interrupt.", serialport.in_waiting) 39 | if serialport.in_waiting: #If there is content in the serial buffer, read it and act on it 40 | #if True: #This is a testing line 41 | print('Data in waiting') 42 | data = str(serialport.read(serialport.in_waiting).hex()) #This produces a list of nibbles (half bytes) 43 | data = leftovers + data #Append any leftover data for evaluation 44 | 45 | commands = [] 46 | commands, leftovers = parseData(data, gaspacsHex) 47 | print("Commands:" + str(commands)) 48 | 49 | for command in commands: 50 | # print(command) 51 | await packet.processPacket(command) #Process Command Packets 52 | print("Made it all the way. Leftovers: ", leftovers) 53 | # serialport.reset_input_buffer() 54 | await asyncio.sleep(5) 55 | else: #No contents in serial buffer 56 | print('buffer empty') 57 | await asyncio.sleep(5) 58 | 59 | # serialport.close() 60 | except Exception as e: 61 | print("Failure to run interrupt. Exception:", repr(e)) 62 | await asyncio.sleep(5) 63 | 64 | def parseData(data, bracket): #Takes data string, in the form of hex, from async read serial function. Spits out all AX.25 packets and GASPACS packets contained inside, as well as remaining data to be put into the leftovers 65 | # fileChecker.fullReset() 66 | try: 67 | searching = True 68 | gaspacsPackets = [] 69 | modifiedString = data 70 | while searching: #Searching for packets bracketed by Hex-bytes of 'GASPACS' 71 | content = None 72 | content, modifiedString, searching = searchGASPACS(modifiedString, bracket) 73 | if searching: 74 | gaspacsPackets.append(content) 75 | 76 | return gaspacsPackets, modifiedString 77 | except Exception as e: 78 | print("Failed in parse Data. Error:", e) 79 | return 0, 0, 0 80 | 81 | 82 | def searchGASPACS(data, str): #Must be passed as a string of hex, for both parameters 83 | #Finds command or window packets, bracketed by in . Removes the brackets and the contents in between from . Returns the command contents 84 | # fileChecker.fullReset() 85 | try: 86 | content='' 87 | occurences = findOccurences(data, str) 88 | modifiedString = data 89 | changed = False 90 | i=0 91 | if (0= 0): 51 | min = line 52 | minLine = i 53 | contents.remove(minLine) 54 | self.__fileChecher.checkFile(self.__filepath) 55 | file = open(self.__filepath, "w") 56 | if(not delete): 57 | file.write(minLine + "\n") 58 | for j in contents: 59 | line = j.split(',') 60 | if line[0] != '': 61 | if(int(line[0]) >= 0): 62 | file.write(j + "\n") 63 | file.close() 64 | if(not delete): 65 | return int (min[0]) 66 | else : 67 | return minLine 68 | 69 | def clearQueue(self): 70 | self.__fileChecher.checkFile(self.__filepath) 71 | self.__fileChecher.windowProtection() 72 | file = open(self.__filepath, "w") 73 | file.close() -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .Drivers import * 2 | -------------------------------------------------------------------------------- /docs/CubeWorks_UML.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallSatGasTeam/CubeWorks/d615cb27b897289bb3a5305b91c62256718685a1/docs/CubeWorks_UML.png -------------------------------------------------------------------------------- /docs/DataBase schema Proposal.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallSatGasTeam/CubeWorks/d615cb27b897289bb3a5305b91c62256718685a1/docs/DataBase schema Proposal.docx -------------------------------------------------------------------------------- /docs/Electrical_notes.md: -------------------------------------------------------------------------------- 1 | # Electrical notes on devices 2 | 3 | ## Magnetometer/Accelerometer 4 | Pins: 5 | | Name | GPIO number | Pin Number| 6 | | --- | --- | --- | 7 | | SDA | 2 | 3 | 8 | | SCL | 3 | 5 | 9 | | INT1 | 16 | 36 | 10 | | DRDY | 18 | 12 | 11 | 12 | The values for this will vary greatly depending on the orientation of the computer board. 13 | Expected readings are: 14 | ``` 15 | Acceleration (m/s^2): X=-1.606 Y=-0.650 Z=9.523 16 | Magnetometer (micro-Teslas): X=19.182 Y=4.909 Z=-39.490 17 | ``` 18 | If lying flat, the Z values should be similar to those above. 19 | 20 | ## ADC 21 | Pins: 22 | | Name | GPIO number | Pin Number| 23 | | --- | --- | --- | 24 | | MISO | 9 | 21 | 25 | | MOSI | 10 | 19 | 26 | | SCLK | 11 | 23 | 27 | | CS | 25 | 22 | 28 | 29 | *CS is active low. Setting the pin low allows you to communicate with the ADC 30 | 31 | Channels: 32 | | Sensor | ADC channel | 33 | | --- | --- | 34 | | UV sensor | 1 | 35 | | Sun sensor 1 | 5 | 36 | | Sun sensor 2 | 4 | 37 | | Sun sensor 3 | 2 | 38 | | Sun sensor 4 | 3 | 39 | | Sun sensor 5 | 0 | 40 | 41 | *The channels are weird, but it made the traces on the board work out nicely. 42 | 43 | ### UV sensor 44 | The UV sensor circuit is tuned to that any UV light will cause the circuit to max out at around 3.3V (3.29V in testing) 45 | This is converted by the ADC into a 12 bit binary number, with 0 as 0V and 4096 as 3.3V. 46 | When the sensor is exposed to UV light, it should read around 3.3V. 47 | 48 | ### Sun sensors 49 | The Sun sensors have almost the exact same circuit as the UV sensor, except that they are tuned so that the max amount of sunlight the sensor can read corresponds to just below 3.3V in the circuit. 50 | 51 | ## EPS 52 | The EPS module is connected on the I2C bus, with a seperate pin for an EPS reset. 53 | 54 | Pins: 55 | | Name | GPIO number | Pin Number| 56 | | --- | --- | --- | 57 | | SDA | 2 | 3 | 58 | | SCL | 3 | 5 | 59 | | EPS_RST | 6 | 31 | 60 | 61 | *EPS_RST is active low, setting the pin low will reset the EPS 62 | 63 | Command 1 will give the battery voltage from the EPS as a 12 bit number (0-4096). 64 | Multiply this by 0.0023394775 to get the voltage. 65 | The raw battery bus should read between 3.5 - 4.12 V 66 | 67 | ## Solar Panel Temperature Sensors 68 | The solar panels are connected to the Pi on the SPI bus. 69 | 70 | Pins: 71 | | Name | GPIO number | Pin Number| 72 | | --- | --- | --- | 73 | | MISO | 9 | 21 | 74 | | MOSI | 10 | 19 | 75 | | SCLK | 11 | 23 | 76 | | Panel_1_Temp_CS | 7 | 26 | 77 | | Panel_2_Temp_CS | 8 | 24 | 78 | 79 | The accuracy of the Temperature sensors are: 80 | | Condition | Error margin | 81 | | --- | --- | 82 | | -25°C to 85°C | ±1.5°C | 83 | | -55°C to 125°C | ±2.0°C | 84 | 85 | ## Chronodot 86 | The chronodot is connected to the Pi on the I2C bus, along with a reset pin, an interrupt, and a 32kHz square wave 87 | 88 | Pins: 89 | | Name | GPIO number | Pin Number| 90 | | --- | --- | --- | 91 | | SDA | 2 | 3 | 92 | | SCL | 3 | 5 | 93 | | RST | 22 | 15 | 94 | | INT | 23 | 16 | 95 | | 32kHz | 27 | 13 | 96 | 97 | *The RST pin is active low, setting the RST pin low will cause the chronodot to reset 98 | 99 | *The INT pin is also active low. 100 | 101 | The RST pin acts as both an external reset button, but also an internal reset indicator. The RST pin will go low if the suply voltage for the chronodot goes too low and the chronodot resets. This can indicate a comprimised clock. 102 | 103 | The INT pin acts like an alarm. A specific time can be set, and at that time, the chronodot will set the INT pin low. Otherwise this pin is high. 104 | 105 | The accuracy of the clock is ±2 minutes/year of operation 106 | 107 | ## Wire cutters 108 | The wire cutters have an on switch as two inhibits. 109 | 110 | Pins: 111 | | Name | GPIO number | Pin Number| 112 | | --- | --- | --- | 113 | | WC1_on | 4 | 7 | 114 | | WC1_safety1 | 20 | 38 | 115 | | WC1_safety2 | 16 | 36 | 116 | | WC2_on | 5 | 29 | 117 | | WC2_safety1 | 26 | 37 | 118 | | WC2_safety2 | 19 | 35 | 119 | 120 | The inhibits must be set High and the on pin must be set Low to turn the wire cutters on. 121 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # CubeWorks Documentation 2 | 3 | CubeWorks is documented using Doxygen, a free and simple automatic documentation generator. 4 | 5 | ## Building the Docs 6 | 7 | The output directory is set to be ignored by git so the documentation is not committed to source control. To build the CubeWorks documentation, do the following: 8 | 9 | ### Install Doxygen 10 | 11 | To install Doxygen, simply run the following command from a linux shell: 12 | 13 | `$ sudo apt install doxygen` 14 | 15 | If you are using a Windows or MacOS system, you can either install the windows version as outlined in the Doxygen setup instructions (http://www.doxygen.nl/manual/install.html) or install the Windows Subsystem for Linux and run the command there. There is also Homebrew entry for Doxygen for Mac users. 16 | 17 | ### Run Doxygen 18 | 19 | Doxygen is already configured to build the documentation using the `Doxyfile` file included in the root directory of the project. Simply use the following command: 20 | 21 | `$ doxygen Doxyfile` 22 | 23 | Doxygen will run and output the documentation in `GASPACS/docs/doxygen`. To view the documentation as a web page, `cd` into `GASPACS/docs/doxygen/html` and run `index.html` or double click on `index.html` in your file browser. Doxygen also generates LaTeX documents for the project. 24 | -------------------------------------------------------------------------------- /docs/testing.md: -------------------------------------------------------------------------------- 1 | #Testing Procedures 2 | -------------------------------------------------------------------------------- /flightConfig.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | char cammand0 [] = "sudo crontab -l >> crontab_new ; echo @reboot sudo /usr/bin/tvservice -o >> crontab_new ; sudo crontab crontab_new ; rm crontab_new"; 5 | char cammand1 [] = "sudo cat /boot/config.txt >> config_temp ; sudo echo dtparam=act_led_trigger=none >> config_temp"; 6 | char cammand2 [] = "sudo echo dtparam=act_led_activelow=on >> config_temp "; 7 | char cammand3 [] = "sudo echo dtoverlay=disable-wifi >> config_temp"; 8 | char cammand4 [] = "sudo echo dtoverlay=disable-bt >> config_temp"; 9 | char cammand5 [] = "sudo cp config_temp /boot/config.txt ; rm config_temp"; 10 | 11 | /************************************************************************* 12 | * This is the code that will set the pi into the power saving options we 13 | * want to use. 14 | * NOTE: This dispable wifi as well, this is the finial flight 15 | * configuration 16 | ************************************************************************/ 17 | void main() 18 | { 19 | system(cammand0); 20 | system(cammand1); 21 | system(cammand2); 22 | system(cammand3); 23 | system(cammand4); 24 | system(cammand5); 25 | system("sudo reboot"); 26 | } 27 | 28 | //this code will update all the code bases 29 | //Written by Shawn -------------------------------------------------------------------------------- /flightConfigWifi.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | char cammand0 [] = "sudo crontab -l >> crontab_new ; echo @reboot sudo /usr/bin/tvservice -o >> crontab_new ; sudo crontab crontab_new ; rm crontab_new"; 5 | char cammand1 [] = "sudo cat /boot/config.txt >> config_temp ; sudo echo dtparam=act_led_trigger=none >> config_temp"; 6 | char cammand2 [] = "sudo echo dtparam=act_led_activelow=on >> config_temp "; 7 | char cammand5 [] = "sudo cp config_temp /boot/config.txt ; rm config_temp"; 8 | 9 | /************************************************************************* 10 | * This is the code that will set the pi into the power saving options we 11 | * want to use. 12 | * NOTE: This does not dispable wifi as well, this is for testing finial 13 | * configuration 14 | ************************************************************************/ 15 | void main() 16 | { 17 | system(cammand0); 18 | system(cammand1); 19 | system(cammand2); 20 | system(cammand5); 21 | system("sudo reboot"); 22 | } 23 | 24 | //this code will update all the code bases 25 | //Written by Shawn -------------------------------------------------------------------------------- /flightLogic/DummymissionModes/README.md: -------------------------------------------------------------------------------- 1 | Mission Modes 2 | == 3 | Antenna Deploy Mode: 4 | -- 5 | Location: ../flightLogic/missionModes/antennaDeploy.py 6 | 7 | Functionality: 8 | In the file flightLogic.py there is a section of code that will check if the antennas have been deployed using our antenna door driver (see GASPACS Software I: the Drivers). If this check returns false the antenna deploy mode will run. This mode will check our battery to see if we have enough power to open the doors. If that check returns true we will then call our back up antenna deployment driver (see GASPACA Software I: the Drivers). If we do not have enough power we will go into sage mode. 9 | 10 | Pre Boom Deployment mode: 11 | -- 12 | Location: ../flightLogic/missionModes/preBoomDeploy.py 13 | 14 | Functionality: 15 | When we go into Pre Boom Deployment mode we perform multiple checks. These checks are to insure the satellite is exposed to the sun where our Aero Boom can cure and stiffen as well as to make sure we have enough power. To get the data needed (TTNC, Attitude and Boom Deployment data) to perform these checks the Pre Boom Deployment Mode code will call the getDriverData class. 16 | 17 | Boom Deployment mode: 18 | --- 19 | Location: ../flightLogic/missionModes/boomDeploy.py 20 | 21 | Functionality: 22 | The Boom Deployment Mode will run after all the checks performed by the Pre-Boom Deployment mode. The Boom Deployment mode will run the Boom Deployer Driver and then the Camera Driver. (see GASPACA Software I: the Drivers) 23 | 24 | Post-Boom Deploy mode: 25 | -- 26 | Location: ../flightLogic/missionModes/postBoomDeploy.py 27 | 28 | Functionality: 29 | This is the last mission mode the Raspberry Pi enters. Once all modes have been run this mode will run a Reboot loop. This loop will cause a reboot every twenty-four hours. This mode will monitor the battery power and other basic functions to keep our satellite from dying. This is also the mode that transmission will take place in. 30 | 31 | 32 | SAFE: 33 | -- 34 | Location: ../flightLogic/missionModes/safe.py 35 | 36 | Functionality: 37 | This mode will turn the Raspberry Pi off. It does this by sending a signal to the Arduino Beetle to turn it off. This is to protect against damage to the Pi and lose of data. 38 | 39 | 40 | -------------------------------------------------------------------------------- /flightLogic/DummymissionModes/antennaDeploy.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../../') 3 | import time 4 | from DummyDrivers.backupAntennaDeployer import BackupAntennaDeployer 5 | from DummyDrivers.antennaDoor import AntennaDoor 6 | import DummyDrivers.eps.EPS as EPS 7 | import asyncio 8 | from flightLogic.DummymissionModes import safe 9 | import flightLogic.DummygetDriverData as getDriverData 10 | from TXISR import pythonInterrupt 11 | 12 | 13 | class antennaMode: 14 | 15 | def __init__(self, saveobject): 16 | self.deployVoltage = 3 #Threshold voltage to deploy 17 | self.maximumWaitTime = 30 #Maximum time to wait for deployment before going to SAFE 18 | self.timeWaited = 0 #Time already waited - zero 19 | self.__getDataTTNC = getDriverData.TTNCData(saveobject) 20 | self.__getDataAttitude = getDriverData.AttitudeData(saveobject) 21 | self.__tasks = [] #List will be populated with background tasks to cancel them 22 | self.__safeMode = safe.safe(saveobject) 23 | self.__antennaDeployer = BackupAntennaDeployer() 24 | self.__antennaDoor = AntennaDoor() 25 | 26 | 27 | async def run(self): 28 | print('Antenna Deploy Running!') 29 | ttncData = self.__getDataTTNC 30 | attitudeData = self.__getDataAttitude 31 | self.__tasks.append(asyncio.create_task(pythonInterrupt.interrupt())) 32 | self.__tasks.append(asyncio.create_task(ttncData.collectTTNCData(1))) #Antenna deploy is mission mode 1 33 | self.__tasks.append(asyncio.create_task(attitudeData.collectAttitudeData())) 34 | self.__tasks.append(asyncio.create_task(self.__safeMode.thresholdCheck())) #Check battery conditions, run safe mode if battery drops below safe level 35 | self.__tasks.append(asyncio.create_task(self.__safeMode.heartBeat())) 36 | eps=EPS() 37 | while True: #Runs antenna deploy loop 38 | if (eps.getBusVoltage()>self.deployVoltage): 39 | await asyncio.gather(self.__antennaDeployer.deployPrimary()) #Fire Primary Backup Resistor 40 | doorStatus = self.__antennaDoor.readDoorStatus() #Check Door status 41 | if doorStatus == (1,1,1,1): #probably need to change this to actually work 42 | self.cancelAllTasks(self.__tasks) 43 | print('Doors are open, returning true') 44 | return True 45 | else: 46 | print('Firing secondary, primary did not work. Returning True') 47 | await asyncio.gather(self.__antennaDeployer.deploySecondary()) 48 | self.cancelAllTasks(self.__tasks) 49 | return True 50 | else: 51 | if(self.timeWaited > self.maximumWaitTime): 52 | self.__safeMode.run(10) #1 hour 53 | await asyncio.sleep(5) #This is an artifact of testing, and will not matter for the actual flight software 54 | else: 55 | #Wait 1 minute 56 | print('Waiting 1 minute until battery status resolves') 57 | self.timeWaited = self.timeWaited+1 58 | await asyncio.sleep(60) 59 | 60 | 61 | 62 | def cancelAllTasks(self, taskList): 63 | try: 64 | for t in taskList: 65 | t.cancel() 66 | except asyncio.exceptions.CancelledException: 67 | print("Caught thrown exception in cancelling background task") 68 | -------------------------------------------------------------------------------- /flightLogic/DummymissionModes/boomDeploy.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | sys.path.append('../../') 4 | import asyncio 5 | from flightLogic.DummymissionModes import safe 6 | from flightLogic.DummygetDriverData import * 7 | import DummyDrivers.boomDeployer as boomDeployer 8 | import DummyDrivers.camera.Camera as camera 9 | from TXISR import pythonInterrupt 10 | 11 | 12 | 13 | class boomMode: 14 | def __init__(self, saveobject): 15 | self.__getDataTTNC = TTNCData(saveobject) 16 | self.__getDataAttitude = AttitudeData(saveobject) 17 | self.__getDataDeployData = DeployData(saveobject) 18 | self.__tasks = [] # Empty list will be populated with all background tasks 19 | self.safeMode = safe.safe(saveobject) 20 | self.saveobject = saveobject 21 | 22 | async def run(self): 23 | # Setting up background processes 24 | ttncData = self.__getDataTTNC 25 | attitudeData = self.__getDataAttitude 26 | deployData = DeployData(self.saveobject) 27 | self.__tasks.append(asyncio.create_task(pythonInterrupt.interrupt())) 28 | self.__tasks.append(asyncio.create_task(ttncData.collectTTNCData(3))) # Boom deploy is mode 3 29 | self.__tasks.append(asyncio.create_task(attitudeData.collectAttitudeData())) 30 | self.__tasks.append(asyncio.create_task(deployData.collectDeployData())) 31 | self.__tasks.append(asyncio.create_task(self.safeMode.thresholdCheck())) 32 | self.__tasks.append(asyncio.create_task(self.safeMode.heartBeat())) 33 | 34 | # Deploy boom, take picture 35 | await asyncio.sleep(5) 36 | deployer = boomDeployer.BoomDeployer() 37 | cam = camera.Camera() 38 | await deployer.deploy() #From LOGAN: Deployer.deploy is now an asyncio method, run it like the others 39 | #cam.takePicture() 40 | #cam.compressLowResToFiles() No longer necessary 41 | #cam.compressHighResToFiles() No longer necessary 42 | await asyncio.sleep(5) 43 | self.cancelAllTasks(self.__tasks) # Cancel all background tasks 44 | return True # Go to post-boom deploy 45 | 46 | def cancelAllTasks(self, taskList): 47 | try: 48 | for t in taskList: 49 | t.cancel() 50 | except asyncio.exceptions.CancelledException: 51 | print("Caught thrown exception in cancelling background task") 52 | -------------------------------------------------------------------------------- /flightLogic/DummymissionModes/postBoomDeploy.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import subprocess 3 | import os 4 | import time 5 | sys.path.append('../../') 6 | import asyncio 7 | from flightLogic.DummymissionModes import safe 8 | from flightLogic.DummygetDriverData import * 9 | import DummyDrivers.eps.EPS as EPS 10 | from TXISR import prepareFiles 11 | from TXISR import pythonInterrupt 12 | from protectionProticol.fileProtection import FileReset 13 | 14 | fileChecker = FileReset() 15 | 16 | TRANSFER_WINDOW_BUFFER_TIME = 30 #30 seconds 17 | REBOOT_WAIT_TIME = 900 #15 minutes, 900 seconds 18 | 19 | class postBoomMode: 20 | 21 | def __init__(self, saveobject): 22 | self.postBoomTimeFile = open("postBoomTime.txt", "w+") 23 | self.__getDataTTNC = TTNCData(saveobject) 24 | self.__getDataAttitude = AttitudeData(saveobject) 25 | self.__tasks = [] # List will be populated with all background tasks 26 | self.__safeMode = safe.safe(saveobject) 27 | self.__timeToNextWindow = -1 28 | self.___nextWindowTime = -1 29 | self.__duration = -1 30 | self.__datatype = -1 31 | self.__pictureNumber = -1 32 | self.__startFromBeginning = -1 33 | fileChecker.checkFile("../TXISR/data/transmissionFlag.txt") 34 | fileChecker.checkFile("../TXSIR/data/txWindows.txt") 35 | self.__transmissionFlagFile = open("../TXISR/data/transmissionFlag.txt") 36 | self.__txWindowsPath = ("../TXISR/data/txWindows.txt") 37 | 38 | async def run(self): 39 | #Set up background processes 40 | ttncData = self.__getDataTTNC 41 | attitudeData = self.__getDataAttitude 42 | self.__tasks.append(asyncio.create_task(pythonInterrupt.interrupt())) 43 | self.__tasks.append(asyncio.create_task(ttncData.collectTTNCData(4))) #Post-boom is mode 4 44 | self.__tasks.append(asyncio.create_task(attitudeData.collectAttitudeData())) 45 | self.__tasks.append(asyncio.create_task(self.__safeMode.thresholdCheck())) 46 | fileChecker.checkFile("../TXISR/data/txWindows.txt") 47 | self.__tasks.append(asyncio.create_task(self.__safeMode.heartBeat())) 48 | self.__tasks.append(asyncio.create_task(self.readNextTransferWindow())) 49 | self.__tasks.append(asyncio.create_task(self.rebootLoop())) 50 | while True: 51 | #if close enough, prep files 52 | #wait until 5 seconds before, return True 53 | if(self.__timeToNextWindow is not -1 and self.__timeToNextWindow<60): #If next window is in 2 minutes or less 54 | if(self.__datatype < 3): #Attitude, TTNC, or Deployment data 55 | prepareFiles.prepareData(self.__duration, self.__datatype) 56 | else: 57 | prepareFiles.preparePicture(self.__duration, self.__datatype, self.pictureNumber) 58 | break 59 | await asyncio.sleep(5) 60 | windowTime = self.__nextWindowTime 61 | while True: 62 | if((windowTime-time.time()) <= 5): 63 | # Check the files before adding them as objects 64 | fileChecker.checkFile("../TXISR/data/transmissionFlag.txt") 65 | txisrCodePath = '../TXISR/TXServiceCode/TXService.run' 66 | os.system(txisrCodePath + ' ' + str(self.__datatype)) #Call TXISR Code 67 | return True 68 | await asyncio.sleep(0.1) #Check at 10Hz until the window time gap is less than 5 seconds 69 | 70 | async def rebootLoop(self): 71 | upTime = 0 72 | while True: 73 | if upTime>86400: #Live for more than 24 hours 74 | if (self.__timeToNextWindow == -1) or (self.__timeToNextWindow > REBOOT_WAIT_TIME): 75 | self.__safeMode.run(1) #restart, powering off Pi for 1 minute 76 | print('Rebooting raspberry pi') 77 | upTime=0 78 | else: 79 | print('Uptime: '+str(upTime)) 80 | await asyncio.sleep(60) 81 | upTime += 60 82 | 83 | async def readNextTransferWindow(self, transferWindowFilename): 84 | while True: 85 | #read the given transfer window file and extract the data for the soonest transfer window 86 | fileChecker.checkFile(transferWindowFilename) 87 | transferWindowFile = open(transferWindowFilename) 88 | sendData = 0 89 | soonestWindowTime = 0 90 | for line in transferWindowFile: 91 | #print("reading line: ") 92 | #print(line) 93 | data = line.split(",") 94 | #data[0] = time of next window, data[1] = duration of window, data[2] = datatype, data[3] = picture number 95 | if(float(data[0]) - time.time() > TRANSFER_WINDOW_BUFFER_TIME): #if the transfer window is at BUFFER_TIME milliseconds in the future 96 | if(soonestWindowTime == 0 or float(data[0]) - time.time() < soonestWindowTime): 97 | soonestWindowTime = float(data[0]) - time.time() 98 | sendData = data 99 | if not(sendData == 0): 100 | #print("Found next transfer window: ") 101 | #print(sendData) 102 | self.__timeToNextWindow = float(sendData[0]) - time.time() 103 | self.__duration = int(sendData[1]) 104 | self.__datatype = int(sendData[2]) 105 | self.__pictureNumber = int(sendData[3]) 106 | self.__nextWindowTime = float(sendData[0]) 107 | #print(self.__timeToNextWindow) 108 | #print(self.__duration) 109 | #print(self.__datatype) 110 | #print(self.__pictureNumber) 111 | await asyncio.sleep(10) #Checks transmission windows every 10 seconds 112 | 113 | def cancellAllTasks(self, taskList): #Isn't used in this class, but here anyways 114 | try: 115 | for t in taskList: 116 | t.cancel() 117 | except asyncio.exceptions.CancelledException: 118 | print("Caught thrown exception in cancelling background task") 119 | -------------------------------------------------------------------------------- /flightLogic/DummymissionModes/preBoomDeploy.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../../') 3 | import asyncio 4 | from flightLogic.DummymissionModes import safe 5 | from flightLogic.DummygetDriverData import * 6 | from DummyDrivers.eps import EPS as EPS 7 | from DummyDrivers.sunSensors import sunSensorDriver as sunSensorDriver 8 | from TXISR import pythonInterrupt 9 | 10 | 11 | class preBoomMode: 12 | def __init__(self, saveobject): 13 | self.thresholdVoltage = 3.5 #Threshold voltage to deploy AeroBoom. 14 | self.criticalVoltage = 3.1 #Critical voltage, below this go to SAFE 15 | self.darkVoltage = 1 #Average voltage from sunsors that, if below this, indicates GASPACS is in darkness 16 | self.darkMinutes = 1 #How many minutes GASPACS must be on the dark side for before moving forward 17 | self.lightMinimumMinutes = 1 #Minimum amount of time GASPACS must be on light side of orbit before deploying 18 | self.lightMaximumMinutes = 60 #Maximum amount of time GASPACS may be on light side of orbit before deploying, must be less than 90 by a fair margin since less than half of orbit can be sun 19 | self.batteryStatusOk = False 20 | self.maximumWaitTime = 240 #Max time GASPACS can wait, charging batteries, before SAFEing 21 | self.timeWaited = 0 22 | self.sunlightData = [] 23 | self.__getDataTTNC = TTNCData(saveobject) 24 | self.__getDataAttitude = AttitudeData(saveobject) 25 | self.__tasks = [] #Will be populated with tasks 26 | self.saveobject = saveobject 27 | self.safeMode = safe.safe(saveobject) 28 | 29 | async def run(self): 30 | ttncData = self.__getDataTTNC 31 | attitudeData = self.__getDataAttitude 32 | self.__tasks.append(asyncio.create_task(pythonInterrupt.interrupt())) 33 | self.__tasks.append(asyncio.create_task(ttncData.collectTTNCData(2))) #Pre-Boom is mode 2 34 | self.__tasks.append(asyncio.create_task(attitudeData.collectAttitudeData())) 35 | self.__tasks.append(asyncio.create_task(self.safeMode.thresholdCheck())) 36 | self.__tasks.append(asyncio.create_task(self.safeMode.heartBeat())) 37 | self.__tasks.append(asyncio.create_task(self.sunCheck())) 38 | self.__tasks.append(asyncio.create_task(self.batteryCheck())) 39 | while True: #iterate through array, checking for set amount of dark minutes, then set amount of light minutes no greater than the maximum. When light minutes are greater than the maximum, empties array 40 | i=0 41 | darkLength = 0 42 | lastDark = 0 43 | while i < len(self.sunlightData): #Loop through sunlightData, checking for X minutes of darkness 44 | if(self.sunlightData[i]self.darkMinutes*6): #If GASPACS has been in dark longer than the preset amount 49 | lastDark = i 50 | break 51 | i+=1 52 | print('Last Dark ' + str(lastDark)) 53 | 54 | if lastDark != 0: #Condition from previous while loop has been met 55 | q=lastDark 56 | lightLength = 0 57 | print("In Sunlight, looking for min minutes") 58 | while q < len(self.sunlightData): 59 | if(self.sunlightData[q]>=self.darkVoltage): 60 | lightLength+=1 61 | else: 62 | lightLength = 0 #Maybe lightLength -=1 to avoid 1 bad measurement resetting everything 63 | 64 | if(lightLength>self.lightMaximumMinutes*6): #Has been in the light for too long 65 | self.sunlightData.clear() #Reset array of data 66 | break 67 | if(lightLength>self.lightMinimumMinutes*6 and self.batteryStatusOk==True): 68 | self.cancelAllTasks(self.__tasks) #Cancel all background processes 69 | print('Returning and exiting') 70 | return True #Go on to Boom Deploy Mode if the battery is Ok 71 | q += 1 72 | await asyncio.sleep(15) #Run this whole while loop every 15 seconds 73 | 74 | async def sunCheck(self): 75 | sunSensor = sunSensorDriver.sunSensor() 76 | while True: #Monitor the sunlight, record it in list NOTE: could be improved to halve calls 77 | #print('Checking sunlight: '+str(self.sunlightData)) 78 | vList = sunSensor.read() 79 | averageVoltage = sum(vList)/len(vList) 80 | await asyncio.sleep(5) 81 | averageVoltage += sum(vList)/len(vList) 82 | self.sunlightData.append(averageVoltage/2) 83 | await asyncio.sleep(5) #Every 10 seconds, record average solar panel voltage. Rough running average with two pieces to avoid jumps in avg. voltage 84 | 85 | async def batteryCheck(self): 86 | eps = EPS() 87 | while True: #Checking the battery voltage to see if it's ready for deployment, if it is too low for too long --> SAFE 88 | if (eps.getBusVoltage()>self.thresholdVoltage): 89 | print('Battery above threshold voltage for deployment') 90 | self.batteryStatusOk=True 91 | self.timeWaited = 0 92 | await asyncio.sleep(5) 93 | else: 94 | self.batteryStatusOk=False 95 | 96 | if(self.timeWaited*12 > self.maximumWaitTime): #5 seconds every wait 97 | self.safeMode.run(10) #1 hour 98 | print('Battery too low for too long. Rebooting') 99 | self.timeWaited = 0 100 | await asyncio.sleep(5) 101 | else: 102 | #Wait 5 more seconds 103 | self.timeWaited = self.timeWaited+1 104 | await asyncio.sleep(5) #Check battery every 5 seconds 105 | 106 | def cancelAllTasks(self, taskList): #Isn't used in this class, but here anyways 107 | try: 108 | for t in taskList: 109 | t.cancel() 110 | except asyncio.exceptions.CancelledException: 111 | print("Caught thrown exception in cancelling background task") 112 | -------------------------------------------------------------------------------- /flightLogic/DummymissionModes/safe.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../../') 3 | import DummyDrivers.eps.EPS as EPS 4 | import asyncio 5 | import RPi.GPIO as GPIO 6 | from os import system 7 | from time import sleep 8 | import smbus 9 | ##################################################################################### 10 | #All this class does is tell the arduino to shut off the pi for the specified amount 11 | #of time. 12 | #times: to pass to the run func 13 | #pass 1 for 1 min 14 | #pass 2 for 2 min 15 | #pass 3 for 3 min 16 | #pass 10 for an hour 17 | #pass 60 for 6 hours 18 | #pass 120 for 12 hours 19 | #pass 24 for a day 20 | ##################################################################################### 21 | class safe: 22 | def __init__(self, saveObject): 23 | #Setup I2C bus for communication 24 | self.DEVICE_ADDR = 0x08 25 | self.bus = smbus.SMBus(1) 26 | self.__eps = EPS() 27 | self.thresholdVoltage = 3.33 #Threshold Voltage 28 | self.__saveObject = saveObject 29 | #GPIO.setwarnings(False) 30 | #GPIO.setmode(GPIO.BOARD) #Physical Pin numbering NOTE: 8/14/20, this threw an error 31 | #GPIO.setup(40, GPIO.OUT, initial=GPIO.LOW) #Sets pin 40 to be an output pin and sets the initial value to low (off) 32 | 33 | 34 | 35 | def run(self, time): 36 | #send message to the arduino to power off the pi 37 | #make sure we are not about to tx 38 | if(self.__saveObject is not None and self.__saveObject.checkTxWindow()): 39 | #self.bus.write_byte(self.DEVICE_ADDR, time) 40 | print('Sent power-off command') 41 | else: 42 | #self.bus.write_byte(self.DEVICE_ADDR, time) 43 | print('Send power-off command') 44 | 45 | sleep(15) #If Pi hasn't turned off by now, must take drastic measures. Kill heartbeat code! 46 | #system('pkill -9 python') 47 | print("killing heartbeat code") 48 | 49 | async def thresholdCheck(self): 50 | while True: 51 | epsVoltage = self.__eps.getBusVoltage() 52 | if epsVoltage < self.thresholdVoltage: 53 | print("Going into SAFE. eps voltage was "+ str(epsVoltage)) 54 | #self.run(10) #1 hour 55 | else: 56 | print('Threshold is good') 57 | await asyncio.sleep(1) #check voltage every second 58 | 59 | async def heartBeat(self): #Sets up up-and-down voltage on pin 40 for heartbeat with Arduino 60 | waitTime = 4 61 | while True: 62 | #GPIO.output(40, GPIO.HIGH) 63 | print("Heartbeat wave high") 64 | await asyncio.sleep(waitTime/2) 65 | #GPIO.output(40, GPIO.LOW) 66 | print("Heartbeat wave low") 67 | await asyncio.sleep(waitTime/2) 68 | -------------------------------------------------------------------------------- /flightLogic/README.md: -------------------------------------------------------------------------------- 1 | GASPACS Software: The Flight Software 2 | == 3 | Opening Note: 4 | The Flight Logic Plan Outline document goes into greater details on the individual mission modes. This document will explain things more simply. Please read Flight Logic Plan Outline. 5 | 6 | Flight Logic/the Main File: 7 | -- 8 | Location: ../flightLogic/flightLogic.py 9 | 10 | Functionality: 11 | This file will manage all our mission modes outlines in the Flight Logic Plan Outline. All these modes will be run in other files except for the BOOT mission mode. This will be run inside the file flightLogic.py. This file will run all the other mission modes and make sure we are going through them in the right order and completing them successfully. This file also gets Attitude, Boom Deployment and TTNC data gathered by the getDriverData.py file. 12 | 13 | Getting the Data From the Drivers: 14 | -- 15 | Location: ../flightLogic/flightLogic.py 16 | 17 | Functionality: 18 | This file calls on the drivers to gather and organize the TTNC, Boom Deployment and Attitude data. This data is then used in the flightLogic.py file to determine if we are able to execute certain modes. 19 | 20 | Boot Records and Backup Boot Records: 21 | -- 22 | Location: ../flightLogic/bootRecords 23 | ../flightLogic/backupBootRecords 24 | 25 | Purpose: 26 | These are simple text files. They will store in the first line how many times we have booted, in the second line whether we are on the first boot, in the third line whether the antenna have been deployed and in the fourth line what our last mission mode was. We have multiple files with this same data to prevent losing the data. 27 | 28 | Mission Modes 29 | == 30 | Antenna Deploy Mode: 31 | -- 32 | Location: ../flightLogic/missionModes/antennaDeploy.py 33 | 34 | Functionality: 35 | In the file flightLogic.py there is a section of code that will check if the antennas have been deployed using our antenna door driver (see GASPACS Software I: the Drivers). If this check returns false the antenna deploy mode will run. This mode will check our battery to see if we have enough power to open the doors. If that check returns true we will then call our back up antenna deployment driver (see GASPACA Software I: the Drivers). If we do not have enough power we will go into sage mode. 36 | 37 | Pre Boom Deployment mode: 38 | -- 39 | Location: ../flightLogic/missionModes/preBoomDeploy.py 40 | 41 | Functionality: 42 | When we go into Pre Boom Deployment mode we perform multiple checks. These checks are to insure the satellite is exposed to the sun where our Aero Boom can cure and stiffen as well as to make sure we have enough power. To get the data needed (TTNC, Attitude and Boom Deployment data) to perform these checks the Pre Boom Deployment Mode code will call the getDriverData class. 43 | 44 | Boom Deployment mode: 45 | -- 46 | Location: ../flightLogic/missionModes/boomDeploy.py 47 | 48 | Functionality: 49 | The Boom Deployment Mode will run after all the checks performed by the Pre-Boom Deployment mode. The Boom Deployment mode will run the Boom Deployer Driver and then the Camera Driver. (see GASPACA Software I: the Drivers) 50 | 51 | Post-Boom Deploy mode: 52 | -- 53 | Location: ../flightLogic/missionModes/postBoomDeploy.py 54 | 55 | Functionality: 56 | This is the last mission mode the Raspberry Pi enters. Once all modes have been run this mode will run a Reboot loop. This loop will cause a reboot every twenty-four hours. This mode will monitor the battery power and other basic functions to keep our satellite from dying. This is also the mode that transmission will take place in. 57 | 58 | 59 | SAFE: 60 | -- 61 | Location: ../flightLogic/missionModes/safe.py 62 | 63 | Functionality: 64 | This mode will turn the Raspberry Pi off. It does this by sending a signal to the Arduino Beetle to turn it off. This is to protect against damage to the Pi and lose of data. 65 | 66 | 67 | -------------------------------------------------------------------------------- /flightLogic/flightLogicPsuedo.md: -------------------------------------------------------------------------------- 1 | Psuedocode for Flight Logic 2 | -- 3 | STARTING NOTES: 4 | 5 | NOTE: drivers running at different hz must be done inside drivers depending on mission mode. 6 | 7 | MAIN 8 | -- 9 | This file will control running all the mission mode files. 10 | kinda like a parent or a guardian. 11 | 12 | runLogic(drivers, context, lock #Note:entry conditions): 13 | CheckSafe(): 14 | If power < CritPower 15 | Go into safe mode 16 | 17 | checkBootMode(): 18 | If antenna deployed == True: 19 | Resume = Get most recent datapoint from db 20 | Mission mode = resume 21 | Else: 22 | Wait for 30 + x minutes 23 | If antenna deployed == false: 24 | Deploy antenna 25 | Mission mode = ANTENNA DEPLOY 26 | Else: 27 | Mission mode = PRE-BOOM DEPLOY 28 | 29 | 30 | 31 | SAFE 32 | -- 33 | This mission mode will put the satellite into what is basically 34 | low power mode. 35 | 36 | Send flag to watchdog telling Pi to power off 37 | 38 | NOTE: Watchdog needs to handle exit conditions for SAFE 39 | 40 | Antenna Deploy 41 | -- 42 | This mission mode will be run as many times as it takes to 43 | deploy the antenna. If needs be it will go to safe. this will work 44 | very closely with the antennaDoor Driver. 45 | 46 | antennaDeploy: 47 | NOTE: steps 2,3 and 4 from flight logic doc are in the antenna deploy driver. 48 | checkPower(): 49 | While time elapsed < TimeOut: 50 | If eps value in context > CritPower 51 | deployment(): 52 | Antenna.deployed = true; 53 | Break out of loop 54 | Else: 55 | wait(1 min) 56 | If time elapsed > TimeOut: 57 | Go to SAFE 58 | 59 | deployment(): 60 | Call antenna deploy driver 61 | 62 | 63 | boomDeploy(): 64 | If boomDeploy conditions met: 65 | deployment(): 66 | Call boom deploy driver 67 | takePicture() 68 | Boom.deployed = true; 69 | Mission mode = POST-BOOM DEPLOY 70 | 71 | NOTE: post boom deploy step 2? 72 | 73 | Transmission mode 74 | -- 75 | This file will set up a communication line with the ground 76 | and will work call the code under the TXISR file. 77 | 78 | transmission: 79 | evalPower(): 80 | If tx window imminent: 81 | If enough power: 82 | Wait for tx window + offset 83 | transmit() 84 | 85 | 86 | NOTE: Trying to conserve power for the TX 87 | -------------------------------------------------------------------------------- /flightLogic/missionModes/README.md: -------------------------------------------------------------------------------- 1 | Mission Modes 2 | == 3 | Antenna Deploy Mode: 4 | -- 5 | Location: ../flightLogic/missionModes/antennaDeploy.py 6 | 7 | Functionality: 8 | In the file flightLogic.py there is a section of code that will check if the antennas have been deployed using our antenna door driver (see GASPACS Software I: the Drivers). If this check returns false the antenna deploy mode will run. This mode will check our battery to see if we have enough power to open the doors. If that check returns true we will then call our back up antenna deployment driver (see GASPACA Software I: the Drivers). If we do not have enough power we will go into sage mode. 9 | 10 | Pre Boom Deployment mode: 11 | -- 12 | Location: ../flightLogic/missionModes/preBoomDeploy.py 13 | 14 | Functionality: 15 | When we go into Pre Boom Deployment mode we perform multiple checks. These checks are to insure the satellite is exposed to the sun where our Aero Boom can cure and stiffen as well as to make sure we have enough power. To get the data needed (TTNC, Attitude and Boom Deployment data) to perform these checks the Pre Boom Deployment Mode code will call the getDriverData class. 16 | 17 | Boom Deployment mode: 18 | --- 19 | Location: ../flightLogic/missionModes/boomDeploy.py 20 | 21 | Functionality: 22 | The Boom Deployment Mode will run after all the checks performed by the Pre-Boom Deployment mode. The Boom Deployment mode will run the Boom Deployer Driver and then the Camera Driver. (see GASPACA Software I: the Drivers) 23 | 24 | Post-Boom Deploy mode: 25 | --- 26 | Location: ../flightLogic/missionModes/postBoomDeploy.py 27 | 28 | Functionality: 29 | This is the last mission mode the Raspberry Pi enters. Once all modes have been run this mode will run a Reboot loop. This loop will cause a reboot every twenty-four hours. This mode will monitor the battery power and other basic functions to keep our satellite from dying. This is also the mode that transmission will take place in. 30 | Heartbeat: 31 | --- 32 | 33 | Functionality: 34 | The code in Heartbeat.py is the ‘Heartbeat’ of the Raspberry Pi. It sends a five macro-second pulse every four seconds over a pin that connects theRaspberry Pi to the Arduino Beetle where the Watchdog ‘listens’ for the pulse. 35 | 36 | Transmitting: 37 | --- 38 | This code handles watching the tx windows, preparing the data 20 seconds before it is time to transmit, and calling the transmission runtine when it is time to transmit. -------------------------------------------------------------------------------- /flightLogic/missionModes/antennaDeploy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Contains the antennaMode class and the functions: run, cancelAllTasks, and 3 | skipToPostBoom 4 | """ 5 | import sys 6 | #import subprocess 7 | import os 8 | sys.path.append('../../') 9 | import time 10 | from Drivers.backupAntennaDeployer import BackupAntennaDeployer 11 | from Drivers.antennaDoor import AntennaDoor 12 | import Drivers.eps.EPS as EPS 13 | import asyncio 14 | import flightLogic.getDriverData as getDriverData 15 | from TXISR import pythonInterrupt 16 | from TXISR.packetProcessing import packetProcessing as packet 17 | from flightLogic.missionModes.heartBeat import heart_beat 18 | from inspect import currentframe, getframeinfo 19 | 20 | BattVoltageMin = 3.5 21 | BattVoltageMax = 5.1 22 | 23 | class antennaMode: 24 | """ 25 | Deploys the antenna when the battery voltage is high enough. 26 | Instantiated in mainFlightLogic. 27 | """ 28 | "safeModeObject was deleted below in the init parameters after saveObject" 29 | def __init__(self, saveObject, transmitObject, packetObj): 30 | self.deployVoltage = 3.85 #Threshold voltage to deploy 31 | self.maximumWaitTime = 22 * 60 #Maximum time to wait for deployment before going to SAFE 32 | self.timeWaited = 0 #Time already waited - zero 33 | self.__getTTNCData = getDriverData.TTNCData(saveObject) 34 | self.__getAttitudeData = getDriverData.AttitudeData(saveObject) 35 | self.__tasks = [] #List will be populated with background tasks to cancel them 36 | # self.__safeMode = safeModeObject 37 | self.__antennaDeployer = BackupAntennaDeployer() 38 | self.__antennaDoor = AntennaDoor() 39 | self.__transmit = transmitObject 40 | self.__packetProcessing = packetObj 41 | self.__heartBeatObj = heart_beat() 42 | 43 | 44 | async def run(self): 45 | """ 46 | Deploys the antenna when the battery voltage is high enough. 47 | Runs async tasks pythonInterrupt, collectTTNCData, collectAttitudeData, 48 | thresholdcheck, skipToPostBoom, readNextTransferWindow, trasmit 49 | """ 50 | print('Antenna Deploy Running!') 51 | self.__tasks.append(asyncio.create_task(self.__heartBeatObj.heartBeatRun())) 52 | self.__tasks.append(asyncio.create_task(pythonInterrupt.interrupt(self.__transmit, self.__packetProcessing))) 53 | self.__tasks.append(asyncio.create_task(self.__getTTNCData.collectTTNCData(1))) #Antenna deploy is mission mode 1 54 | self.__tasks.append(asyncio.create_task(self.__getAttitudeData.collectAttitudeData())) 55 | 56 | 57 | eps=EPS() 58 | #If ground station has sent command to skip to post boom 59 | # if await self.skipToPostBoom(): 60 | # return True #Finish this mode and move on 61 | while True: #Runs antenna deploy loop 62 | try: 63 | BattVoltage = eps.getBusVoltage() 64 | if ((BattVoltage < BattVoltageMin) or (BattVoltage > BattVoltageMax)): 65 | print("BattVoltageInt: ", BattVoltage, "BattVoltage: ", BattVoltage) 66 | raise unexpectedValue 67 | except Exception as e: 68 | BattVoltage = 4.18 69 | print("failed to retrieve BattVoltage. Exception: ", repr(e), 70 | getframeinfo(currentframe()).filename, getframeinfo(currentframe()).lineno, 71 | "Received: ", BattVoltage) 72 | 73 | if (BattVoltage > self.deployVoltage): #If the bus voltage is high enough 74 | await asyncio.gather(self.__antennaDeployer.deployPrimary()) #Fire Primary Backup Resistor 75 | try: 76 | doorStatus = self.__antennaDoor.readDoorStatus() #Returns True if all doors are deployed 77 | except: 78 | doorStatus = False 79 | print("Failed to check door status") 80 | if doorStatus == True: 81 | self.cancelAllTasks(self.__tasks) 82 | print('Doors are open, returning true') 83 | return True 84 | else: 85 | print('Firing secondary, primary did not work. Returning True') 86 | await asyncio.gather(self.__antennaDeployer.deploySecondary()) 87 | self.cancelAllTasks(self.__tasks) 88 | return True 89 | else: 90 | if(self.timeWaited > self.maximumWaitTime): 91 | await asyncio.gather(self.__antennaDeployer.deployPrimary()) #Fire Primary Backup Resistor 92 | try: 93 | doorStatus = self.__antennaDoor.readDoorStatus() #Returns True if all doors are deployed 94 | except: 95 | print("Failed to check door status") 96 | if doorStatus == True: 97 | self.cancelAllTasks(self.__tasks) 98 | print('Doors are open, returning true') 99 | return True 100 | else: 101 | print('Firing secondary, primary did not work. Returning True') 102 | await asyncio.gather(self.__antennaDeployer.deploySecondary()) 103 | self.cancelAllTasks(self.__tasks) 104 | return True 105 | else: 106 | #Wait 1 minute 107 | print('Waiting 1 minute until battery status resolves') 108 | self.timeWaited = self.timeWaited+1 109 | await asyncio.sleep(60) 110 | 111 | def cancelAllTasks(self, taskList): 112 | """ 113 | Cancels all async tasks created at the beginning of run. 114 | """ 115 | try: 116 | for t in taskList: 117 | t.cancel() 118 | except asyncio.exceptions.CancelledException: 119 | print("Caught thrown exception in cancelling background task") 120 | -------------------------------------------------------------------------------- /flightLogic/missionModes/boomDeploy.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | sys.path.append('../../') 4 | import asyncio 5 | from flightLogic.getDriverData import * 6 | import Drivers.boomDeployer as boomDeployer 7 | from TXISR import pythonInterrupt 8 | from TXISR.packetProcessing import packetProcessing as packet 9 | from flightLogic.missionModes.heartBeat import heart_beat 10 | 11 | #safeModeObject was deleted below in the init parameters after saveObject 12 | class boomMode: 13 | def __init__(self, saveObject, transmitObject, cam, packetObj): 14 | self.__getTTNCData = TTNCData(saveObject) 15 | self.__getAttitudeData = AttitudeData(saveObject) 16 | self.__getDeployData = DeployData(saveObject) 17 | self.__tasks = [] # Empty list will be populated with all background tasks 18 | # self.__safeMode = safeModeObject 19 | self.__saveObject = saveObject 20 | self.__transmit = transmitObject 21 | self.__packetProcessing = packet(transmitObject, cam) 22 | self.__heartBeatObj = heart_beat() 23 | self.__cam = cam 24 | self.__packetProcessing = packetObj 25 | 26 | async def run(self): 27 | # Setting up background processes 28 | self.__tasks.append(asyncio.create_task(self.__heartBeatObj.heartBeatRun())) 29 | self.__tasks.append(asyncio.create_task(pythonInterrupt.interrupt(self.__transmit, self.__packetProcessing))) 30 | self.__tasks.append(asyncio.create_task(self.__getTTNCData.collectTTNCData(3))) # Boom deploy is mode 3 31 | self.__tasks.append(asyncio.create_task(self.__getAttitudeData.collectAttitudeData())) 32 | self.__tasks.append(asyncio.create_task(self.__getDeployData.collectDeployData())) 33 | 34 | print("Starting boom deploy") 35 | # Deploy boom, take picture 36 | await asyncio.sleep(5) 37 | 38 | deployer = boomDeployer.BoomDeployer() 39 | await deployer.deploy() 40 | 41 | 42 | try: 43 | print("Taking picture") 44 | self.__cam.takePicture() 45 | except Exception as e: 46 | print("Failed to take a picture because we received excpetion:", repr(e)) 47 | await asyncio.sleep(5) 48 | # await asyncio.sleep(60) #sleep if a transmission is running 49 | self.cancelAllTasks(self.__tasks) # Cancel all background tasks 50 | return True # Go to post-boom deploy 51 | 52 | def cancelAllTasks(self, taskList): 53 | try: 54 | for t in taskList: 55 | t.cancel() 56 | except asyncio.exceptions.CancelledException: 57 | print("Caught thrown exception in cancelling background task") 58 | -------------------------------------------------------------------------------- /flightLogic/missionModes/heartBeat.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import RPi.GPIO as GPIO 3 | 4 | class heart_beat: 5 | def __init__(self) -> None: 6 | GPIO.setwarnings(False) 7 | GPIO.setmode(GPIO.BCM) #Physical Pin numbering 8 | GPIO.setup(21, GPIO.OUT, initial=GPIO.LOW) #Sets pin 40 (GPIO 21) to be an output pin and sets the initial value to low (off) 9 | 10 | 11 | async def heartBeatRun(self): #Sets up up-and-down voltage on pin 40 (GPIO 21) for heartbeat with Arduino 12 | waitTime = 4 13 | while True: 14 | GPIO.output(21, GPIO.HIGH) 15 | print("Heartbeat wave high") 16 | await asyncio.sleep(waitTime/2) 17 | GPIO.output(21, GPIO.LOW) 18 | print("Heartbeat wave low") 19 | await asyncio.sleep(waitTime/2) -------------------------------------------------------------------------------- /flightLogic/missionModes/postBoomDeploy.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../../') 3 | import asyncio 4 | from flightLogic.getDriverData import * 5 | from TXISR import pythonInterrupt 6 | from flightLogic.missionModes.heartBeat import heart_beat 7 | 8 | # safeModeObject was deleted below in the init parameters after saveObject 9 | class postBoomMode: 10 | def __init__(self, saveObject, transmitObject, packetObj): 11 | self.__transmit = transmitObject 12 | self.__getTTNCData = TTNCData(saveObject) 13 | self.__getAttitudeData = AttitudeData(saveObject) 14 | self.__heartBeatObj = heart_beat() 15 | self.__tasks = [] # List will be populated with all background tasks 16 | # self.__safeMode = safeModeObject 17 | print("Initialized postBoomDeploy") 18 | self.__packet = packetObj 19 | 20 | async def run(self): 21 | #Set up background processes 22 | print("Inside of run in postBoomDeploy") 23 | self.__tasks.append(asyncio.create_task(self.__heartBeatObj.heartBeatRun())) 24 | self.__tasks.append(asyncio.create_task(pythonInterrupt.interrupt(self.__transmit, self.__packet))) 25 | self.__tasks.append(asyncio.create_task(self.__getTTNCData.collectTTNCData(4))) #Post-boom is mode 4 26 | self.__tasks.append(asyncio.create_task(self.__getAttitudeData.collectAttitudeData())) 27 | 28 | print("Initalized all tasks.") 29 | while True: 30 | # Don't remove this print statement, the while loop is an integral part 31 | # of making sure that postBoomDeploy doesn't crash and it needs to 32 | # have something in there so it doesn't crash and suffer 33 | print('This is post Boom Deploy') 34 | await asyncio.sleep(10) 35 | 36 | def cancellAllTasks(self, taskList): #Isn't used in this class, but here anyways 37 | try: 38 | for t in taskList: 39 | t.cancel() 40 | except asyncio.exceptions.CancelledException: 41 | print("Caught thrown exception in cancelling background task") 42 | -------------------------------------------------------------------------------- /flightLogic/missionModes/preBoomDeploy.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../../') 3 | import asyncio 4 | from flightLogic.getDriverData import * 5 | from Drivers.eps import EPS as EPS 6 | from Drivers.sunSensors import sunSensorDriver 7 | from TXISR import pythonInterrupt 8 | 9 | from inspect import currentframe, getframeinfo 10 | from TXISR.packetProcessing import packetProcessing as packet 11 | from flightLogic.missionModes.heartBeat import heart_beat 12 | 13 | 14 | sunSensorMin = 0.0 15 | sunSensorMax = 3.3 16 | getBusVoltageMin = 3.5 17 | getBusVoltageMax = 5.1 18 | 19 | 20 | DEBUG = False 21 | 22 | #safeModeObject was deleted below in the init parameters after saveObject 23 | class preBoomMode: 24 | def __init__(self, saveObject, transmitObject, packetObj): 25 | self.thresholdVoltage = 3.85 #Threshold voltage to deploy AeroBoom. 26 | self.darkVoltage = 0.9 #Average voltage from sunsors that, if below this, indicates GASPACS is in darkness 27 | self.lightMinimumMinutes = 1 #Minimum amount of time GASPACS must be on light side of orbit before deploying 28 | self.batteryStatusOk = False 29 | self.sunlightData = 0 30 | self.__getTTNCData = TTNCData(saveObject) 31 | self.__getAttitudeData = AttitudeData(saveObject) 32 | self.__tasks = [] #Will be populated with tasks 33 | self.__packetProcessing = packetObj 34 | self.__transmit = transmitObject 35 | self.__heartBeatObj = heart_beat() 36 | 37 | 38 | async def run(self): 39 | self.__tasks.append(asyncio.create_task(self.__heartBeatObj.heartBeatRun())) 40 | self.__tasks.append(asyncio.create_task(pythonInterrupt.interrupt(self.__transmit, self.__packetProcessing))) 41 | self.__tasks.append(asyncio.create_task(self.__getTTNCData.collectTTNCData(2))) #Pre-Boom is mode 2 42 | self.__tasks.append(asyncio.create_task(self.__getAttitudeData.collectAttitudeData())) 43 | self.__tasks.append(asyncio.create_task(self.sunCheck())) 44 | self.__tasks.append(asyncio.create_task(self.batteryCheck())) 45 | 46 | """This code was intended to find out when to deploy the boom based on how long GASPACS was in the light. 47 | This was commented out because there is no need for this functionality if there is no resin in the boom""" 48 | while True: 49 | print("checking for sunlight") 50 | 51 | if ((self.sunlightData > self.darkVoltage) and self.batteryStatusOk == True): 52 | self.cancelAllTasks(self.__tasks) #Cancel all background processes, this depolys the boom basically 53 | print('Returning and exiting') 54 | return True #Go on to Boom Deploy Mode if the battery is Ok 55 | await asyncio.sleep(5) #Run this whole while loop every 15 seconds 56 | 57 | async def sunCheck(self): 58 | sunSensor = sunSensorDriver.sunSensor() 59 | while True: #Monitor the sunlight, record it in list NOTE: could be improved to halve calls 60 | vList = [0.0, 0.0, 0.0, 0.0, 0.0] 61 | try: 62 | vList = sunSensor.read() 63 | if DEBUG: 64 | print("Pre boom deploy sun sensor values: ", vList) 65 | size = 0 66 | while size < 5: 67 | if (vList[size] < sunSensorMin) or (vList[size] > sunSensorMax): 68 | raise unexpectedValue 69 | size += 1 70 | except Exception as e: 71 | print("Failure to pull sunSensor data. Received error:", repr(e), 72 | getframeinfo(currentframe()).filename, getframeinfo(currentframe()).lineno) 73 | vList[0] = 1 74 | 75 | self.sunlightData = max(vList) 76 | await asyncio.sleep(5) 77 | 78 | async def batteryCheck(self): 79 | eps = EPS() 80 | while True: #Checking the battery voltage to see if it's ready for deployment, if it is too low for too long --> SAFE 81 | try: 82 | BusVoltage = eps.getBusVoltage() 83 | #NOTE: This line is for testing! DO NOT 84 | if(BusVoltage < getBusVoltageMin) or (BusVoltage > getBusVoltageMax): 85 | raise unexpectedValue 86 | except Exception as e: 87 | BusVoltage = 4.18 88 | print("Failed to retrieve BusVoltage, got", BusVoltage, "instead. Received error: ", 89 | repr(e), getframeinfo(currentframe()).filename, getframeinfo(currentframe()).lineno) 90 | 91 | if (BusVoltage > self.thresholdVoltage): 92 | print('Battery above threshold voltage for deployment') 93 | self.batteryStatusOk=True 94 | await asyncio.sleep(5) 95 | else: 96 | self.batteryStatusOk=False 97 | 98 | #Wait 5 more seconds 99 | await asyncio.sleep(5) #Check battery every 5 seconds 100 | 101 | def cancelAllTasks(self, taskList): #Isn't used in this class, but here anyways 102 | try: 103 | for t in taskList: 104 | t.cancel() 105 | except asyncio.exceptions.CancelledException: 106 | print("Caught thrown exception in cancelling background task") 107 | 108 | class unexpectedValue(Exception): 109 | print("Received unexpected value.") 110 | pass 111 | -------------------------------------------------------------------------------- /flightLogic/postBoomTime.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallSatGasTeam/CubeWorks/d615cb27b897289bb3a5305b91c62256718685a1/flightLogic/postBoomTime.txt -------------------------------------------------------------------------------- /flightLogic/saveTofiles.py: -------------------------------------------------------------------------------- 1 | """ 2 | These are the classes that handle saving the data to the files 3 | 4 | Each class has one func that will write all the data in a list that 5 | is passed to it. 6 | 7 | They are written as different classes so that each code my be quicker 8 | 9 | NOTE: try to only instantiate the classes once. if you have to do 10 | more this will not cause a problem it will only slow the code 11 | down. 12 | """ 13 | import os.path 14 | import asyncio 15 | import time 16 | from protectionProticol.fileProtection import FileReset 17 | 18 | 19 | fileChecker = FileReset() 20 | 21 | class save: 22 | def __init__(self): 23 | pass 24 | 25 | #write the data to the file, 26 | #NOTE: it is important that you put a : after the time stamp, this will 27 | #effect the txisr 28 | async def writeTTNC(self, data): 29 | fileChecker.checkFile("/home/pi/flightLogicData/TTNC_Data.txt") 30 | TTNC_File = open('/home/pi/flightLogicData/TTNC_Data.txt', "a+") 31 | TTNC_File.write(str(data)+'\n') 32 | TTNC_File.close() 33 | 34 | 35 | #this is data collection for Deploy 36 | #write the data to the file, 37 | #NOTE: it is important that you put a : after the time stamp, this will 38 | #effect the txisr 39 | async def writeDeploy(self, data): 40 | fileChecker.checkFile("/home/pi/flightLogicData/Deploy_Data.txt") 41 | Deploy_File = open("/home/pi/flightLogicData/Deploy_Data.txt", "a+") 42 | Deploy_File.write(str(data)+'\n') 43 | Deploy_File.close() 44 | 45 | 46 | #this part of the code is for data collection of attitude data 47 | #write the data to the file, 48 | #NOTE: it is important that you put a : after the time stamp, this will 49 | #effect the txisr 50 | async def writeAttitude(self, data): 51 | fileChecker.checkFile("/home/pi/flightLogicData/Attitude_Data.txt") 52 | Attitude_File = open("/home/pi/flightLogicData/Attitude_Data.txt", "a+") 53 | Attitude_File.write(str(data)+'\n') 54 | Attitude_File.close() 55 | 56 | -------------------------------------------------------------------------------- /install.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | //this will handle the install of all gas software 6 | char GIT_CODE_BASE [] ="https://github.com/SmallSatGasTeam/CubeWorks.git"; 7 | char crontabComand [] ="@reboot sudo runuser pi -c ./startup.exe"; 8 | 9 | 10 | //to change the branch take out the -b codeBase for master or change it to a different branch. 11 | char cammand0 [] = "git clone https://github.com/SmallSatGasTeam/CubeWorks.git CubeWorks0/"; 12 | char cammand1 [] = "git clone https://github.com/SmallSatGasTeam/CubeWorks.git CubeWorks1/"; 13 | char cammand2 [] = "git clone https://github.com/SmallSatGasTeam/CubeWorks.git CubeWorks2/"; 14 | char cammand3 [] = "git clone https://github.com/SmallSatGasTeam/CubeWorks.git CubeWorks3/"; 15 | char cammand4 [] = "git clone https://github.com/SmallSatGasTeam/CubeWorks.git CubeWorks4/"; 16 | 17 | //this code compiles the c code that is need to run transmission. 18 | char branchCommand0 [] = "cd ; cd CubeWorks0/TXISR/TXServiceCode ; gcc TXServiceCode.c -o TXService.run; cd ;"; 19 | char branchCommand1 [] = "cd ; cd CubeWorks1/TXISR/TXServiceCode ; gcc TXServiceCode.c -o TXService.run; cd ;"; 20 | char branchCommand2 [] = "cd ; cd CubeWorks2/TXISR/TXServiceCode ; gcc TXServiceCode.c -o TXService.run; cd ;"; 21 | char branchCommand3 [] = "cd ; cd CubeWorks3/TXISR/TXServiceCode ; gcc TXServiceCode.c -o TXService.run; cd ;"; 22 | char branchCommand4 [] = "cd ; cd CubeWorks4/TXISR/TXServiceCode ; gcc TXServiceCode.c -o TXService.run; cd ;"; 23 | 24 | char upDateCommand [] = "cd ; cd CubeWorks0/ ; gcc upDateCode.c -o upDateCode.exe ; cp upDateCode.exe ~/ ; rm upDateCode.exe"; 25 | 26 | char testingCommand [] = "cd ; cd CubeWorks0/ ; gcc setNewTXWindow.c -o setNewTXWindow.exe ; mkdir ../TXISRData; sudo cp setNewTXWindow.exe ~/TXISRData/setNewTXWindow.exe ; rm setNewTXWindow.exe"; 27 | 28 | 29 | 30 | 31 | // #update and install python 32 | // #NO long in use cause the version are lock for FLIGHT! 33 | // # sudo apt full-upgrade 34 | // # sudo apt-get update 35 | // # sudo apt install python3 36 | // # sudo apt install python3-pip 37 | // # sudo apt install python3-numpy 38 | // # sudo apt install git 39 | 40 | void main() 41 | { 42 | printf("\n>>>Creating a CubeWorks0<<<\n"); 43 | //install the first code base 44 | system(cammand0); 45 | 46 | // //install the code bases 47 | printf("\n>>>Creating a CubeWorks1<<<\n"); 48 | system(cammand1); 49 | 50 | printf("\n>>>Creating a CubeWorks2<<<\n"); 51 | system(cammand2); 52 | 53 | printf("\n>>>Creating a CubeWorks3<<<\n"); 54 | system(cammand3); 55 | 56 | printf("\n>>>Creating a CubeWorks4<<<\n"); 57 | system(cammand4); 58 | 59 | 60 | //complie the code 61 | printf("\n>>>Creating tx routine for CubeWorks0<<<\n"); 62 | system(branchCommand0); 63 | printf("\n>>>Creating tx routine for CubeWorks1<<<\n"); 64 | system(branchCommand1); 65 | printf("\n>>>Creating tx routine for CubeWorks2<<<\n"); 66 | system(branchCommand2); 67 | printf("\n>>>Creating tx routine for CubeWorks3<<<\n"); 68 | system(branchCommand3); 69 | printf("\n>>>Creating tx routine for CubeWorks4<<<\n"); 70 | system(branchCommand4); 71 | 72 | //create the upDate code 73 | printf("\n>>>Creating update code<<<\n"); 74 | system(upDateCommand); 75 | 76 | //Create the set new txWindows code 77 | printf("\n>>>Creating setTXWindows routine<<<\n"); 78 | system(testingCommand); 79 | 80 | //create the start up code, and then move it to the root 81 | printf("\n>>>creating multi-code base protocol\n"); 82 | system("cd CubeWorks0\ngcc startup.c -o startup.exe\ncp startup.exe ~/"); 83 | 84 | 85 | //up date the crontab to run the startup.exe 86 | // printf("\n>>>creating start up proticol<<<\n"); 87 | // system("sudo crontab -l > mycron"); 88 | // system("echo @reboot sudo runuser pi -c cd ; ./startup.exe >> mycron"); 89 | // system("sudo crontab mycron"); 90 | // system("rm mycron"); 91 | 92 | 93 | printf(">>rebooting to finish installation<<<\n"); 94 | system("sudo reboot"); 95 | } -------------------------------------------------------------------------------- /lastBase.txt: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /log.txt: -------------------------------------------------------------------------------- 1 | Booted 2 | -------------------------------------------------------------------------------- /mycron: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmallSatGasTeam/CubeWorks/d615cb27b897289bb3a5305b91c62256718685a1/mycron -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | board 2 | RPi.GPIO 3 | adafruit-blinka 4 | adafruit-circuitpython-lsm303_accel 5 | adafruit-circuitpython-lis2mdl 6 | spidev 7 | picamera 8 | smbus 9 | -------------------------------------------------------------------------------- /setNewTXWindow.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* This code provides a simple way to feed a transmission window into txWindows.txt while running the main flight logic. */ 6 | 7 | char command0[] = "date +%s"; 8 | 9 | int main(int argc, char * argv[]) 10 | { 11 | FILE *fptr; 12 | fptr = fopen("/home/pi/TXISRData/txWindows.txt","a+"); 13 | int flag = 0; 14 | int input, dataType, windowsNumber, windowLength, n, i, line, pic, length; 15 | long int Time; 16 | time_t txTime; 17 | 18 | if(fptr == NULL) 19 | { 20 | printf("ERROR WITH FILEPATH\n"); 21 | exit(1); 22 | } 23 | 24 | if(argc == 1){ 25 | printf("You are creating custom txWindows. To create multiple at equal" 26 | " intervals, the usage is: sudo ./setNewTXWindow.c