1# Copyright 2019 Oticon A/S
2# SPDX-License-Identifier: Apache-2.0
3
4#EDTT Transport driver for BabbleSim
5# Any EDTT Transport shall implement the following interface:
6#   __init__(args)
7#   connect()
8#   send(idx, message)
9#   recv(idx, number_bytes, timeout)
10#   wait(time)
11#   close()
12#   get_time()
13#   n_devices : Number of devices it is connected to
14
15import struct;
16import os;
17import signal;
18import stat;
19
20def create_dir(folderpath):
21    if not os.path.exists(folderpath):
22        if ( os.mkdir( folderpath , stat.S_IRWXG | stat.S_IRWXU ) != 0 ) \
23            and ( os.access( folderpath, os.F_OK ) == False ):
24          raise Exception("Cannot create folder %s"% folderpath);
25
26def create_com_folder(sim_id):
27    import getpass
28    Com_path = "/tmp/bs_" + getpass.getuser();
29    create_dir(Com_path);
30    Com_path = Com_path + "/" + sim_id;
31    create_dir(Com_path);
32    return Com_path;
33
34def Create_FIFO_if_not_there(FIFOName):
35    #we try to create a fifo which may already exist, and/or that some other
36    #program may be racing to create at the same time
37    if ( os.access( FIFOName, os.F_OK ) == False ):
38        try:
39            err = os.mkfifo(FIFOName,  stat.S_IRWXG | stat.S_IRWXU);
40        except OSError as e:
41            if (e.errno == 17): #File already exists => we are done
42                return;
43            else:
44                raise Exception("Could not create FIFO %s", FIFOName);
45
46        if (err != 0) and ( os.access( FIFOName, os.F_OK ) == False ):
47            raise Exception("Could not create FIFO %s", FIFOName);
48
49
50class EDTTT:
51    COM_DISCONNECT = 0;
52    COM_WAIT       = 1;
53    COM_SEND       = 2;
54    COM_RCV        = 3;
55
56    TO_EDTT  = 0;
57    TO_BRIDGE =1;
58    FIFOs = [-1, -1];
59    FIFOnames = [ "" , "" ];
60    verbosity = 0;
61    last_t =  0; #last timestamp received from the bridge
62    Connected = False;
63    n_devices = 0;
64
65    def __init__(self, pending_args, TraceClass):
66        self.Trace = TraceClass;
67        import argparse
68        parser = argparse.ArgumentParser(prog="BabbleSim transport options:", add_help=False)
69        parser.add_argument("-s", "--sim_id", required=True, help="Simulation id");
70        parser.add_argument("-d", "--bridge-device-nbr", required=True, help="Device number of the EDTT bridge");
71        (args, discard) = parser.parse_known_args(pending_args)
72
73        self.device_nbr = args.bridge_device_nbr;
74        self.sim_id = args.sim_id;
75
76    def connect(self):
77        Com_path = create_com_folder(self.sim_id);
78
79        self.Trace.trace(3,"Connecting to EDTT bridge");
80
81        #Note that python ignores SIGPIPE by default
82
83        self.FIFOnames[self.TO_EDTT] = \
84            Com_path + "/Device" + str(self.device_nbr) + ".ToPTT";
85        self.FIFOnames[self.TO_BRIDGE] = \
86            Com_path + "/Device" + str(self.device_nbr) + ".ToBridge";
87
88        Create_FIFO_if_not_there(self.FIFOnames[self.TO_EDTT]);
89        Create_FIFO_if_not_there(self.FIFOnames[self.TO_BRIDGE]);
90
91        self.FIFOs[self.TO_BRIDGE] = os.open(self.FIFOnames[self.TO_BRIDGE], os.O_WRONLY);
92        self.FIFOs[self.TO_EDTT] = os.open(self.FIFOnames[self.TO_EDTT], os.O_RDONLY);
93
94        if (self.FIFOs[self.TO_BRIDGE] == -1):
95            raise Exception("Could not open FIFO %s"% self.FIFOnames[self.TO_BRIDGE]);
96
97        if (self.FIFOs[self.TO_EDTT] == -1):
98            raise Exception("Could not open FIFO %s"% self.FIFOnames[self.TO_EDTT]);
99
100        self.Connected = True;
101        self.Trace.trace(4,"Connected to EDTT bridge");
102
103        packet = self.read(2);
104        self.n_devices = struct.unpack('<H',packet[0:2])[0];
105
106    def cleanup(self):
107        self.Trace.trace(4,"Cleaning up transport");
108        try:
109            os.close(self.FIFOs[self.TO_BRIDGE]);
110            self.Trace.trace(9,"Closed FIFO to Bridge");
111            os.close(self.FIFOs[self.TO_EDTT]);
112            self.Trace.trace(9,"Closed FIFO to EDTT");
113            os.remove(self.FIFOnames[self.TO_BRIDGE]);
114            os.remove(self.FIFOnames[self.TO_EDTT]);
115        except OSError:
116            self.Trace.trace(9,"(minor) Error closing FIFO "
117                             "(most likely either file does not exist yet)");
118
119    def disconnect(self):
120        if self.Connected:
121            self.Trace.trace(4,"Disconnecting bridge")
122            self.ll_send(struct.pack('<B', self.COM_DISCONNECT));
123            self.Connected = False;
124
125    def close(self):
126        self.disconnect();
127        self.cleanup();
128
129    def get_last_t(self):
130      return self.last_t
131
132    def ll_send(self, content):
133        try:
134            written = os.write(self.FIFOs[self.TO_BRIDGE], content);
135            if written != len(content):
136                raise;
137        except:
138            self.Trace.trace(4,"The EDTT bridge disappeared when trying to "
139                             "write to it");
140            self.cleanup();
141            raise Exception("Abruptly disconnected from bridge");
142
143    def ll_read(self, nbytes):
144        try:
145            pkt = os.read(self.FIFOs[self.TO_EDTT], nbytes);
146            if len(pkt) == 0:
147                raise;
148            return pkt;
149        except:
150           self.Trace.trace(4,"The EDTT bridge disapeared when trying to read "
151                              "from it");
152           self.cleanup();
153           raise Exception("Abruptly disconnected from bridge");
154
155    def send(self, idx, message):
156        if (idx > self.n_devices -1):
157            raise Exception("Trying to access unconnected device %i"%idx);
158        # build the packet
159        packet = struct.pack('<BBH', self.COM_SEND, idx, len(message)) + message
160        # send the packet
161        self.ll_send(packet)
162        # a send is immediate (no time advance)
163
164    def read(self, nbytes):
165      received_nbytes = 0;
166      packet = bytearray();
167      #print "Will try to pick " + str(nbytes) + " bytes"
168      while ( len(packet) < nbytes):
169        packet += self.ll_read(nbytes - received_nbytes);
170        #print "Got so far " + str(len(packet)) + " bytes"
171        #print 'packet: "' + repr(packet) + '"'
172      return packet;
173
174    def recv(self, idx, number_bytes, to=None):
175        if (idx > self.n_devices -1):
176            raise Exception("Trying to access unconnected device %i"%idx);
177
178        #print ("EDTT: ("+str(idx)+") request rcv of "+ str(number_bytes) + " bytes; to = " + str(to));
179        if to == None:
180            to = self.default_to
181        if ( number_bytes == 0 ):
182          return b""
183
184        timeout = to*1000 + self.last_t;
185        # Poll the bridge for a response
186        #print ("EDTT: ("+str(idx)+") request rcv of "+ str(number_bytes) + " bytes; timeout = " + str(timeout));
187
188        self.ll_send(struct.pack('<BBQH', self.COM_RCV, idx, timeout, number_bytes));
189
190        header = self.read(9);
191
192        #print '"' + repr(header[1:])+ '"'
193        #print "length = " + str(len(header[1:]))
194        self.last_t = struct.unpack('<Q',header[1:])[0];
195        #print "last_t updated to " + str(self.last_t)
196
197        packet=b""
198        if header[0] == 0x00: #correct reception => packet follows
199          #print "correctly received " + str(number_bytes) + " bytes"
200          packet = self.read(number_bytes)
201          if (len(packet) != number_bytes):
202            raise Exception("very weird..")
203          #print "packet itself =" + repr(packet)
204
205        return packet
206
207    def wait(self, delay_in_ms):
208        # pack a message : delay 4 bytes
209        end_of_wait = delay_in_ms*1000 + self.last_t;
210        self.ll_send(struct.pack('<BQ', self.COM_WAIT, end_of_wait));
211        self.last_t = end_of_wait;
212
213    def get_time(self):
214        return self.get_last_t()/1000;
215