Started the second step of the redundant communication link, writing the Link Combiner agent. This agent receives ivy messages from the link agent(s) when they have a name specified, removes duplicate messages, then sends ivy messages of the same format that the link agent used to send messages.

This commit is contained in:
Cameron Lee
2013-03-13 19:32:09 -06:00
parent 09a704e592
commit cc761a9262
2 changed files with 230 additions and 0 deletions
@@ -0,0 +1,28 @@
Created by Cameron Lee (cwlee1@ualberta.ca)
This software enables multiple downlinks in order to provide redundancy in the plane to ground communication (it does not change the ground to plane communication at all). If the same data is sent by the autopilot over two or more radio links, then this software lets these two streams of data be combined into one stream. The resultant string will have all of information transmitted by the autopilot as long as at least one link receives the data. The status of each link can be monitored in the GCS in the LINK tab of the notebook. Various types of links can be used, for example:
- 900 MHz modem
- 2.4 GHz modem
- UDP packets sent over a Wi-Fi network
- Modulated audio signal in a video feed
- Satellite modem
TO USE:
1. First, implement the hardware to have multiple independant streams of data sent to the ground station computer.
2. In Paparazzi Center, run an instance of the Link agent for each stream of data. Configure each Link agent according to it's stream. Also, use the -id flag to give each link a unique id (an integer number).
3. Run the Link Combiner agent.
For example, the following agents run in Paparazzi center will allow 2 links to be used, each of which is connected to it's own USB serial port:
sw/ground_segment/tmtc/server
sw/ground_segment/cockpit/gcs
sw/ground_segment/tmtc/link -d /dev/ttyUSB0 -id 1
sw/ground_segment/tmtc/link -d /dev/ttyUSB1 -id 2
sw/ground_segment/python/redundant_link/link_combiner.py
HOW IT WORKS:
When the link agent is run with the -id flag set, instead of transmitting the data it receives over the ivy bus like normal, it encapsulates it in a TELEMETRY_MESSAGE message which also contains the link id. The Link Combiner listens to these messages from each link and sends data over the ivy bus to the other agents as if it was a link. The Link Combiner also sends the LINK_STATUS message so that the GCS can display the status of each link.
The Link Combiner uses an algorithm to filter out duplicate messages. In other words, if a message is sent by the autopilot over both links and it is received by both links, then it's the same message and should only be handled once by other agents such as the GCS. The Link Combiner's algorithm therefore ignores a message received over any link if it's identical to a message received by another link. This is achieved by keeping a buffer of the last N messages for each link. Once a message has been received by all links, it's removed from the buffer. Also, the buffer is circular, so even if a message isn't received by all links, it will be overwritten after N more messages are received. This algorithm isn't guaranteed to be perfect, but in typical operation, it seems to work very well. And for the application of displaying aircraft data, some missing or duplicate data is acceptable.
+202
View File
@@ -0,0 +1,202 @@
#!/usr/bin/env python
# This file is part of paparazzi.
# paparazzi is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
# paparazzi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with paparazzi; see the file COPYING. If not, write to
# the Free Software Foundation, 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
"""This program listens to ivy messages from the link agent (see link.ml)
when the link's -name arguement is set. It combines messages received from
any number of link agents and sends ivy messages to the Server and other
agents."""
#To-do:
# 1. Implement timestamp on buffer
# 2. Record and print statistics. Necessary to test functionality.
# 3. Move the regex into the message class to make things more modular
# 4. Implement command line options
# 5. Read the link status messages, create new link status data, and send it out over the ivy bus
import logging
import sys
import os
from ivy.std_api import *
#Options (will be implemented as command line options later):
BUFFER_SIZE = 10 #The number of elements messages to be stored in the circular buffer for each link.
class Circular_Buffer:
def __init__(self, size):
self.buffer = [""]*size
self.index = 0
self.size = size
def add(self, contents):
self.buffer[self.index] = contents
self.incr()
def contains(self, contents):
counter = self.index
while 1:
if self.buffer[counter] == contents:
return 1
else:
if counter <= 0:
counter = self.size-1 #Moving the counter in the reverse direction of the index in order to test the most recent contents first (since they're most likely to match)
else:
counter -= 1
if counter == self.index:
return 0
def incr(self):
if self.index >= (self.size - 1):
self.index = 0
else:
self.index += 1
def remove(self, contents):
counter = self.index
while 1:
if self.buffer[counter] == contents:
self.buffer[counter] = ""
else:
if counter <= 0:
counter = self.size-1
else:
counter -= 1
if counter == self.index:
return
def displayContents(self):
for counter in xrange(0,self.size):
if self.index != counter:
print(" %s" %self.buffer[counter])
else:
print("-> %s" %self.buffer[counter])
class Message:
def __init__(self, sender, link_name, raw_message):
self.link_name = link_name
self.raw_message = " ".join(raw_message.split(";"))
def linkName(self):
return self.link_name
def message(self):
return self.raw_message
class Link:
def __init__(self, name, buffer_size=10, verbose=1):
self.buffer = Circular_Buffer(buffer_size)
self.name = name
self.verbose = verbose
pass
def checkBuffer(self,message):
return self.buffer.contains(message.message())
def addToBuffer(self,message):
self.buffer.add(message.message())
if self.verbose:
print("%s Buffer:" % self.name)
self.buffer.displayContents();
def removeFromBuffer(self,message):
self.buffer.remove(message.message())
class Link_Combiner:
def __init__(self):
self.links = {}
self.initIvy()
def initIvy(self):
# initialising the bus
IvyInit("Link_Combiner", # application name for Ivy
"READY", # ready message
0, # main loop is local (ie. using IvyMainloop)
lambda x,y: y, # handler called on connection/deconnection
lambda x,y: y # handler called when a diemessage is received
)
# starting the bus
logging.getLogger('Ivy').setLevel(logging.WARN)
IvyStart("")
IvyBindMsg(self.onIvyMessage, "^([^ ]+) TELEMETRY_MESSAGE ([^ ]+) ([^ ]+) ([^ ]+)$")
def onIvyMessage(self, agent, *larg):
message = Message(larg[1], larg[2], larg[3])
if message.linkName() not in self.links: #Adding a new link
self.links[message.linkName()] = Link(message.linkName(), BUFFER_SIZE)
print("Link Combiner Detected a New Link: %s" %message.linkName())
#Processing messages from an already added link
sent = self.sendMessage(message)
self.bufferMessage(message)
def sendMessage(self, message):
in_buffer = self.checkBuffers(message)
if not in_buffer:
IvySendMsg(message.message())
return True
else:
return False
def checkBuffers(self, message):
#The returned value is the best guess at whether the message is a duplicate (True), or not (False).
#If the message is already in this link's buffer, then taking it as not a duplicate. So returning False. But also, removing it from all buffers. So that when they receive it, they don't do the same.
#If the message is not in this link's buffer, then checking all other buffers and only if it's not in any of them, counting the message as not a duplicate.
match = self.links[message.linkName()].checkBuffer(message)
if match: #Removing the message from all buffers
for link_name in self.links:
self.links[link_name].removeFromBuffer(message)
return False
else: #Checking all other links' buffers
for link_name in self.links:
if link_name == message.linkName():
continue
else:
match = self.links[link_name].checkBuffer(message)
if match:
return True
return False
if match_count == 0:
return False
elif match_count == length(self.links):
for link_name in self.links:
self.links[link_name].removeFromBuffer(message)
def bufferMessage(self, message):
self.links[message.linkName()].addToBuffer(message)
def main():
link = Link_Combiner()
if __name__ == '__main__':
main()