00001
00002
00003 from abc import ABCMeta, abstractmethod, abstractproperty
00004 import sqlalchemy
00005
00006 import rospkg
00007 import rospy
00008 import roslib.message
00009
00010
00011
00012 ros_home = rospkg.get_ros_home()
00013 default_engine_name = 'sqlite:///' + ros_home + '/lama.sqlite'
00014 _engine_name = rospy.get_param('/database_engine', default_engine_name)
00015 del ros_home
00016 del default_engine_name
00017
00018
00019 _interfaces_table_name = 'map_interfaces'
00020
00021
00022 class AbstractDBInterface(object):
00023 __metaclass__ = ABCMeta
00024
00025
00026 engine_name = _engine_name
00027 engine = sqlalchemy.create_engine(_engine_name)
00028 metadata = sqlalchemy.MetaData()
00029 metadata.bind = engine
00030
00031 def __init__(self, interface_name, getter_srv_type, setter_srv_type,
00032 start=False):
00033 """Build the map interface and possibly start ROS services
00034
00035 Parameters
00036 ----------
00037 - interface_name: string, name of the map interface.
00038 - getter_srv_type: string, service message to write into the map.
00039 - setter_srv_type: string, service message to read from the map.
00040 - start: {True|False}, defaults to True. The ROS services for getter and
00041 setter will be started only if start is True. If start is False, the
00042 clients proxies will be None.
00043 """
00044 if '@' in interface_name:
00045 rospy.logerr('@ not allowd in interface name')
00046 raise ValueError('@ not allowd in interface name')
00047
00048 get_srv_class = roslib.message.get_service_class(getter_srv_type)
00049 set_srv_class = roslib.message.get_service_class(setter_srv_type)
00050
00051 rospy.logdebug('Map interface: {} ({}, {})'.format(interface_name,
00052 getter_srv_type,
00053 setter_srv_type))
00054 rospy.logdebug('Getter class {}'.format(get_srv_class))
00055 rospy.logdebug('Getter request slots: {}'.format(
00056 get_srv_class._request_class.__slots__))
00057 rospy.logdebug('Getter response slots: {}'.format(
00058 get_srv_class._response_class.__slots__))
00059 rospy.logdebug('Setter class {}'.format(set_srv_class))
00060 rospy.logdebug('Setter request slots: {}'.format(
00061 set_srv_class._request_class.__slots__))
00062 rospy.logdebug('Setter response slots: {}'.format(
00063 set_srv_class._response_class.__slots__))
00064
00065
00066 self.getter_service_name = self.default_getter_service_name(
00067 interface_name)
00068 self.getter_service_class = get_srv_class
00069 self.getter_service_type = getter_srv_type
00070
00071 self.setter_service_name = self.default_setter_service_name(
00072 interface_name)
00073 self.setter_service_class = set_srv_class
00074 self.setter_service_type = setter_srv_type
00075
00076 self.interface_name = interface_name
00077
00078
00079 self.metadata.reflect()
00080
00081 self.interface_table = self._interface_table()
00082 self.metadata.create_all()
00083
00084 self._generate_schema()
00085
00086
00087 self._getter_service = None
00088 self._setter_service = None
00089 if start:
00090 self.start_services()
00091
00092
00093 self.getter_service_proxy = rospy.ServiceProxy(
00094 self.getter_service_name,
00095 self.getter_service_class)
00096 self.setter_service_proxy = rospy.ServiceProxy(
00097 self.setter_service_name,
00098 self.setter_service_class)
00099
00100 @classmethod
00101 def default_getter_service_name(cls, interface_name):
00102 return interface_name + '_getter'
00103
00104 @classmethod
00105 def default_setter_service_name(cls, interface_name):
00106 return interface_name + '_setter'
00107
00108 @abstractproperty
00109 def interface_type(self):
00110 pass
00111
00112 @abstractmethod
00113 def _generate_schema():
00114 pass
00115
00116 @abstractmethod
00117 def getter_callback(self):
00118 pass
00119
00120 @abstractmethod
00121 def setter_callback(self):
00122 pass
00123
00124 def _interface_table(self):
00125 """Return the table for the type description (may already exists).
00126
00127 Return a table with columns ('interface_name', 'message_type',
00128 interface_type, timestamp_secs, timestamp_nsecs). If the
00129 table already exists, it will be returned.
00130 """
00131 table = sqlalchemy.Table(_interfaces_table_name,
00132 self.metadata,
00133 sqlalchemy.Column('interface_name',
00134 sqlalchemy.String,
00135 unique=True),
00136 sqlalchemy.Column('message_type',
00137 sqlalchemy.String),
00138 sqlalchemy.Column('get_service_type',
00139 sqlalchemy.String),
00140 sqlalchemy.Column('set_service_type',
00141 sqlalchemy.String),
00142 sqlalchemy.Column('interface_type',
00143 sqlalchemy.String),
00144 sqlalchemy.Column('timestamp_secs',
00145 sqlalchemy.BigInteger),
00146 sqlalchemy.Column('timestamp_nsecs',
00147 sqlalchemy.BigInteger),
00148 extend_existing=True)
00149
00150 return table
00151
00152 def _add_interface_description(self):
00153 """Add the inteface description if not already existing
00154
00155 Add the inteface description with unconflicting
00156 (interface_name / message_type / interface_type).
00157
00158 A ValueError is raised if a table with the same name (i.e. same
00159 interface_name) but different message type and interface type already
00160 exists.
00161 """
00162
00163 table = self.interface_table
00164 name = self.interface_name
00165 msg_type = self.getter_service_class._response_class._slot_types[0]
00166
00167 query = table.select(whereclause=(table.c.interface_name == name))
00168 connection = self.engine.connect()
00169 with connection.begin():
00170 result = connection.execute(query).fetchone()
00171
00172 add_interface = True
00173 if result:
00174 add_interface = False
00175 if (result['message_type'] != msg_type or
00176 result['interface_type'] != self.interface_type):
00177 err = ('A table "{}" with message type "{}" and interface ' +
00178 'type "{}" already exists, cannot change to ' +
00179 '"{}"/"{}"').format(
00180 name,
00181 result['message_type'], result['interface_type'],
00182 msg_type, self.interface_type)
00183 rospy.logfatal(err)
00184 raise rospy.ROSException(err)
00185
00186
00187 if add_interface:
00188 insert_args = {
00189 'interface_name': name,
00190 'message_type': msg_type,
00191 'interface_type': self.interface_type,
00192 'get_service_type': self.getter_service_type,
00193 'set_service_type': self.setter_service_type,
00194 }
00195 with connection.begin():
00196 connection.execute(table.insert(), insert_args)
00197 connection.close()
00198
00199 def _get_last_modified(self):
00200 """Return the date of last modification for this interface
00201
00202 Return a rospy.Time instance.
00203 """
00204 table = self.interface_table
00205 name = self.interface_name
00206 query = table.select(whereclause=(table.c.interface_name == name))
00207 connection = self.engine.connect()
00208 with connection.begin():
00209 result = connection.execute(query).fetchone()
00210 connection.close()
00211
00212 if not result:
00213 raise rospy.ServiceException('Corrupted database')
00214 time = rospy.Time(0)
00215 if result['timestamp_secs'] is None:
00216 return time
00217 time.secs = result['timestamp_secs']
00218 time.nsecs = result['timestamp_nsecs']
00219
00220 def _set_timestamp(self, time):
00221 """Set the timestamp to the given time for this interface
00222
00223 Parameters
00224 ----------
00225 - time: a rospy.Time instance.
00226 """
00227 table = self.interface_table
00228 name = self.interface_name
00229 update_args = {
00230 'timestamp_secs': time.secs,
00231 'timestamp_nsecs': time.nsecs,
00232 }
00233 update = table.update().where(table.c.interface_name == name)
00234 connection = self.engine.connect()
00235 with connection.begin():
00236 connection.execute(update, update_args)
00237 connection.close()
00238
00239 def has_table(self, table):
00240 """Return true if table is in the database"""
00241 if table in self.metadata.tables:
00242 return True
00243
00244 self.metadata.reflect()
00245 return table in self.metadata.tables
00246
00247 def start_services(self):
00248 self._getter_service = rospy.Service(self.getter_service_name,
00249 self.getter_service_class,
00250 self.getter_callback)
00251 self._setter_service = rospy.Service(self.setter_service_name,
00252 self.setter_service_class,
00253 self.setter_callback)
00254 rospy.loginfo('Services %s and %s started',
00255 self.getter_service_name, self.setter_service_name)