...
Eg. 0x00 = No GPS Fix, 0 Satellites0x1A = GPS Fix, 10 Satellites0x24 = DGPS Fix, 4 Satellites
Code Block |
---|
|
var checkGPS = function (data) {
if(data !== null) {
var connection_status = ((data & 0xf0) >> 4) > 0; // if theres at least 1 fix
if (connection_status !== this.gps.status) { //if its a different status
this.gps.status = connection_status;
StatusManager.setStatusCode('GPS_LOST', !this.gps.status);
if (this.gps.status === false) { //if it was set to false, start the timer
this.gps.timeSinceLost = Date.now();
}
else {
this.gps.timeSinceLost = null;
}
}
}
}.bind(this);
|
Interpreting autopilot_active
Code Block |
---|
|
var checkPlaneStatus = function (number) {
if(number !== null){
this.initializing = (number === 0);
if (number === 1) { //only set armed to false if the number is ONLY 1
this.armed = false;
}
this.armed = (number === 2);
this.running = (number === 3);
this.killModeWarning = (number === 4);
this.killModeActive = (number === 5);
StatusManager.setStatusCode('AIRCRAFT_INITIALIZE', this.initializing);
StatusManager.setStatusCode('AIRCRAFT_UNARMED', !this.armed);
StatusManager.setStatusCode('AIRCRAFT_ARMED', this.armed);
StatusManager.setStatusCode('AIRCRAFT_RUNNING', this.running);
StatusManager.setStatusCode('AIRCRAFT_KILLMODE_WARNING', this.killModeWarning);
StatusManager.setStatusCode('AIRCRAFT_KILLMODE', this.killModeActive);
}
}.bind(this);
```
### Interpreting wireless connection
|
var checkUHFStatus = function (data) { if(data !== null){ var bitmask = new Bitmask(data); this.uhf.status = bitmask.getBit(1);
Code Block |
---|
|
if (this.uhf.status) { //has been turned to true
this.uhf.timeSinceLost = null;
}
else { //has been turned to false
this.uhf.timeSinceLost = Date.now();
}
StatusManager.setStatusCode('UHF_LOST', !this.uhf.status);
}
|
...
var checkManualMode = function (data) { if(data !== null){ var bitmask = new Bitmask(data); this.manualMode = !bitmask.getBit(0); StatusManager.setStatusCode('MANUAL_MODE', this.manualMode); } }.bind(this);
Code Block |
---|
|
### Interpreting Autonotmous level
```javascript
if (alevel.getBit(0)) {
this.ui.remote_pitch_type.text('Angle');
}
else {
this.ui.remote_pitch_type.text('Rate');
}
if (alevel.getBit(1)) {
this.ui.remote_pitch_source.text('Ground Station');
}
else {
this.ui.remote_pitch_source.text('Controller');
}
if (alevel.getBit(2)) {
this.ui.remote_roll_type.text('Angle');
}
else {
this.ui.remote_roll_type.text('Rate');
}
if (alevel.getBit(3)) {
this.ui.remote_roll_source.text('Ground Station');
}
else {
this.ui.remote_roll_source.text('Controller');
}
if (alevel.getBit(5)) {
this.ui.remote_throttle_source.text('Autopilot');
}
else if (alevel.getBit(4)) {
this.ui.remote_throttle_source.text('Groundstation');
}
else {
this.ui.remote_throttle_source.text('Controller');
}
if (alevel.getBit(6)) {
this.ui.remote_altitude_source.text('Autopilot');
}
else {
this.ui.remote_altitude_source.text('Groundstation');
}
if (alevel.getBit(7)) {
this.ui.remote_altitude_toggle.text('On');
}
else {
this.ui.remote_altitude_toggle.text('Off');
}
if (alevel.getBit(8)) {
this.ui.remote_heading_source.text('Autopilot');
}
else {
this.ui.remote_heading_source.text('Groundstation');
}
if (alevel.getBit(9)) {
this.ui.remote_heading_toggle.text('On');
}
else {
this.ui.remote_heading_toggle.text('Off');
}
if (alevel.getBit(11)) {
this.ui.remote_flap_source.text('Autopilot');
}
else if (alevel.getBit(10)) {
this.ui.remote_flap_source.text('Groundstation');
}
else {
this.ui.remote_flap_source.text('Controller');
}
},
|
...
Data is exported to the data link at a certain frequency (according to a clock). This is done by calling writeDatalink(frequency), where frequency is the time between packets. This subroutine creates a structure (defined in net.h) which contains memory locations for every variable. This data is then pushed to be processed in net_outbound.c.
Code Block |
---|
|
if (time - lastTime > frequency) {
lastTime = time;
struct telem_block* statusData = createTelemetryBlock();
statusData->lat = gps_Latitude;
statusData->lon = gps_Longitude;
...
return pushOutboundTelemetryQueue(statusData);
}
|
When all the data is assembled in the struct, pushOutboundTelemetryQueue(statusData) is called. This pushes the data onto a queue to be processed later:
Code Block |
---|
|
int pushOutboundTelemetryQueue(struct telem_block *telem) {
if (getOutboundQueueLength() >= OUTBOUND_QUEUE_SIZE) {
return -1;
}
outBuffer[outbuff_end] = telem;
outbuff_end++;
outbuff_end = outbuff_end % OUTBOUND_QUEUE_SIZE;
return getOutboundQueueLength();
}
|
...
Every once in a while, the data accumulated must be processed. As a result, every iteration of the program runs a subroutine to maintain and cleanup the circular buffer. For the outgoing buffer, this method is outboundBufferMaintenance():
Code Block |
---|
|
if ( stagingBuffer.sendIndex >= PACKET_LENGTH ) {
destroyTelemetryBlock(stagingBuffer.telemetry.asStruct);
if ( getOutboundQueueLength() ) {
stageTelemetryBlock(popOutboundTelemetryQueue());
}
} else if ( stagingBuffer.telemetry.asStruct == 0 && getOutboundQueueLength() ) {
stageTelemetryBlock(popOutboundTelemetryQueue());
}
|
Note that the structure of stagingBuffer is as follows:
Code Block |
---|
|
struct telem_buffer {
unsigned int sendIndex; // index into telemetry to send
unsigned char header[API_HEADER_LENGTH]; // The header for the telem
union {
struct telem_block *asStruct; // The telemetry block being sent
unsigned char *asArray; // The telemetry intepreted as an array
} telemetry;
unsigned char checksum; // The checksum so far
};
|
...
After sufficient error checking (making sure sendIndex is less than the allowed packet size), stageTelemetryBlock(popOutboundTelemetryQueue()) is called. This method takes (pops) the next struct of data and stages it to be sent. stageTelemetryBlock() is responsible for converting the telemetry data into a telem_buffer structure.
Code Block |
---|
|
void stageTelemetryBlock(struct telem_block *telem) {
stagingBuffer.telemetry.asStruct = telem;
generateApiHeader(stagingBuffer.header, 0);
stagingBuffer.checksum = 0;
// Send index should be reset last for reasons
stagingBuffer.sendIndex = 0;
sendNextByte();
}
|
The first line of the subroutine adds the data into the packet. The second line (generateApiHeader(stagingBuffer.header,0)) creates an appropriate header in the stagingBuffer.header memory address with a data frame of 0. (See the XBEE section for the datasheet). The API header includes information involving which device the packet should be sent to, the length of the packet, as well as acknowledgement options, and packet types (data packet, configuration packet, status packet). After the checksum and sendIndex are explicitly reset, the sending process begins with sendNextByte():
Code Block |
---|
|
void sendNextByte(void) {
unsigned char sendByte; // The byte to send
if ( stagingBuffer.sendIndex < API_HEADER_LENGTH ) {
//while (U2STAbits.TRMT == 0);
sendByte = stagingBuffer.header[stagingBuffer.sendIndex] & 0xFF;
// Compute checksum
if (stagingBuffer.sendIndex >= 3) {
stagingBuffer.checksum += sendByte & 0xFF;
}
} else if ( stagingBuffer.sendIndex < PACKET_LENGTH - 1 ) {
sendByte = stagingBuffer.telemetry.asArray[stagingBuffer.sendIndex - API_HEADER_LENGTH] & 0xFF;
stagingBuffer.checksum += sendByte & 0xFF;
} else if ( stagingBuffer.sendIndex == PACKET_LENGTH - 1) {
sendByte = 0xFF - (stagingBuffer.checksum & 0xFF);
} else {
IFS1bits.U2TXIF = 0;
return;
}
stagingBuffer.sendIndex++;
IFS1bits.U2TXIF = 0;
U2TXREG = sendByte;
}
|
All the "if" statements above, compile the header, the data and the checksum together. Note that the checksum is the bitwise inverse of the actual sum: sendByte = 0xFF - (stagingBuffer.checksum & 0xFF). The most important part of this process is the last line, where each byte is sent to the UART transmit buffer. Since the UART transmit process is interrupt-based, each interrupt keeps calling sendNextByte(), until there is no more data left:
Code Block |
---|
|
void __attribute__((__interrupt__, no_auto_psv)) _U2TXInterrupt(void) {
// Short circuit if nothing in the staging area yet
if ( stagingBuffer.telemetry.asStruct == 0 ) {
IFS1bits.U2TXIF = 0;
return;
}
sendNextByte();
}
|
...
Once every iteration, a command is read from the uplink queue. This is done by calling readDatalink(). The command popCommand() is called. If any new commands have been received, popCommand() will return a command struct (defined in net.h):
Code Block |
---|
|
struct command {
unsigned char cmd;
unsigned char data\_length;
unsigned char data[101];
};
|
It is fairly straight forward. The structure contains a cmd.cmd _which indicates the command ID. This ID corresponds to a certain function that needs to be completed. Following the pop command are a series of case statements (one for each command ID). For instance, if the command ID is 30, the following command is run (in _net_inbound.c):
Code Block |
---|
|
struct command* cmd = popCommand();
//TODO: Add rudimentary input validation
if ( cmd ) {
if (lastCommandSentCode == cmd->cmd){
lastCommandSentCode++;
}
else{
lastCommandSentCode = cmd->cmd * 100;
}
switch (cmd->cmd) {
...
case SET_THROTTLE:
sp_ThrottleRate = (int)(*(int*)(&cmd->data) / 100.0 * (890 - 454) + 454);
break;
...
}
}
|
...
In order for the command structure to exist, the U2RXInterrupt must have been triggered. This occurs when new data is sent.
Code Block |
---|
|
void __attribute__((__interrupt__, no_auto_psv)) _U2RXInterrupt(void) {
unsigned char data = U2RXREG;
if ( rawPacketStatus[packetPos] != BUSY ) { // no buffer available to write
packetPos = ( packetPos + 1 ) % RAW_PACKET_BUFFER_SIZE;
IFS1bits.U2RXIF = 0;
return;
}
switch ( payloadPos ) {
case 0:
if ( data != START_DELIMITER ) {
IFS1bits.U2RXIF = 0;
return;
}
break;
case 1:
if ( data != 0 ) {
payloadPos = 0;
IFS1bits.U2RXIF = 0;
return; // packet length < 100 bytes, so msb == 0
}
break;
case 2:
payloadLength[packetPos] = data;
break;
default: // Normally, don't do anything special
break;
}
rawPackets[packetPos][payloadPos++] = data;
if ( payloadPos && payloadPos == payloadLength[packetPos] + 3 + 1) { // at end of packet
rawPacketStatus[packetPos] = READY;
payloadPos = 0;
packetPos = ( packetPos + 1 ) % RAW_PACKET_BUFFER_SIZE;
if ( rawPacketStatus[packetPos] == EMPTY ) {
rawPacketStatus[packetPos] = BUSY;
}
}
IFS1bits.U2RXIF = 0;
}
|
...
Firstly, the start delimiter is looked for using a case statement. Until the start delimiter is found, nothing happens. Secondly, for case 1 and 2, the length of the packet is check and recorded. Once the length of the packet is known, the data is read into a 2d array called rawPackets. This array contains each byte of every packet in the circular buffer. Once all the data is copied into the array, the packet is marked as READY, and the next one is marked BUSY if it is EMPTY, and the processing of the data begins on the next maintenance cycle when inboundBufferMaintenance() is called from main.c:
Code Block |
---|
|
void inboundBufferMaintenance(void) {
int i;
for ( i = 0; i < RAW_PACKET_BUFFER_SIZE; i++ ) {
if ( rawPacketStatus[i] == READY && checkPacket(rawPackets[i]) ) {
struct command\* cmd = createCommand( rawPackets[i] );
if ( cmd ) { // create command was successful ?
pushCommand( cmd ); // queue it up
rawPacketStatus[i] = EMPTY; // buffer is now good for writing another packet
}
}
}
if ( rawPacketStatus[0] == EMPTY ) {
rawPacketStatus[0] = BUSY;
}
}
|
...