├── README.md ├── python ├── Iridium9602.py ├── __init__.py ├── imap_stuff.py ├── iridium_mo_forward_server.py ├── iridium_mt_forward_server.py ├── iridium_rudics_shore_connection.py ├── sbd_packets.py └── smtp_stuff.py └── setup.py /README.md: -------------------------------------------------------------------------------- 1 | #What is this project? 2 | 3 | This is a Iridium Modem (9602/9603) emulator to make it possible to test your own software again a "Virtual" Iridium device and it connection. 4 | 5 | This application is written in Python language uses pyserial to implement a serial communications interface. The emulator matches the behavior of the Iridium 9602 modem which is available from NAL Research and Rock7Mobile (as Rockblock). The emulator will respond to at commands to write data, execute short-burst data (SBD) sessions, and most of the other functions supported by the 9602 serial interface. 6 | 7 | #What's the use? 8 | 9 | If you want to develop an application on a PC or an embedded device it will to talk to an Iridium modem, you can use this for initial prototyping and testing. This can potentially save quite some cost on Iridium service charges. Also you can already create an application without already buying the real Iridium Modem (9602/9603) hardware. 10 | 11 | # How do I get this software? 12 | 13 | In your Unix shell of choice: 14 | ``` 15 | $ git clone https://github.com/jmalsbury/virtual_iridium 16 | $ cd virtual_iridium/python 17 | $ python Iridium9602.py -d /dev/ttyUSB0 -u youraccount@gmail.com -p your_password -i imap.gmail.com -o smtp.gmail.com -r your_iridium_test_account@gmail.com -m EMAIL 18 | ``` 19 | The specified serial device, in the example above: ttyUSB0 , should connect to the external device that you are developing your Iridium communications app on. You can also use a virtual serial port (like a pair of SOCAT TTYs), to connect to another application on the same PC. 20 | 21 | # Where can I find more documentation? 22 | 23 | This is all I am going to write for now. If I see more people are interested in using this emulator, I may put more effort into this documentation. If you have any question, don't hesitate to e-mail me: jmalsbury [dot] personal [at] gmail [dot] com. 24 | Other Resources 25 | 26 | Iridium 9602 - Developers Manual{TODO}Link to repofile 27 | 28 | Thanks to the original autor J.Malsbury. Maybe his repo is more up to date or has mre features. 29 | [![githalytics.com alpha](https://cruel-carlota.pagodabox.com/bdb824945e9b3e4a959feb550731a1e0 "githalytics.com")](http://githalytics.com/jmalsbury/virtual_iridium) 30 | -------------------------------------------------------------------------------- /python/Iridium9602.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import serial 3 | #from serial.tools import list_ports 4 | import os 5 | from optparse import OptionParser 6 | import io 7 | import time 8 | import random 9 | import sys 10 | from virtual_iridium.smtp_stuff import sendMail 11 | from virtual_iridium.imap_stuff import checkMessages 12 | import socket 13 | import struct 14 | import asyncore 15 | from collections import deque 16 | from virtual_iridium.sbd_packets import assemble_mo_directip_packet 17 | from virtual_iridium.sbd_packets import parse_mt_directip_packet 18 | from virtual_iridium.sbd_packets import assemble_mt_directip_response 19 | 20 | AVERAGE_SBDIX_DELAY = 1 #TODO: implement randomness, average is ~30s 21 | STDEV_SBDIX_DELAY = 1 22 | AVERAGE_SBDIX_SUCCESS = 0.9 23 | 24 | AVERAGE_CSQ_DELAY = 1 25 | STDEV_CSQ_DELAY = 1 26 | 27 | EOL_CHAR = 13 28 | BACKSPACE_CHAR = 8 29 | 30 | REG_STATUS_DETACHED = 0 31 | REG_STATUS_NOT_REGISTER = 1 32 | REG_STATUS_REGISTERED = 2 33 | REG_STATUS_DENIED = 3 34 | 35 | LOCKED = 1 36 | NOT_LOCKED = 0 37 | 38 | echo = True 39 | binary_rx = False 40 | binary_rx_incoming_bytes = 0 41 | 42 | ring_enable = False 43 | 44 | mt_buffer = '' 45 | mo_buffer = '' 46 | mo_set = False 47 | mt_set = True 48 | 49 | momsn = 0 50 | mtmsn = 0 51 | 52 | locked = NOT_LOCKED 53 | 54 | registered = REG_STATUS_NOT_REGISTER 55 | 56 | ser = 0 57 | 58 | lat = 0.0 59 | lon = 0.0 60 | 61 | user = '' 62 | recipient = '' 63 | incoming_server = '' 64 | outgoing_server = '' 65 | password = '' 66 | 67 | mo_ip = '127.0.0.1' 68 | mo_port = 10801 69 | mt_port = 10800 70 | 71 | imei = 300234060379270 72 | 73 | email_enabled = False 74 | ip_enabled = False 75 | http_post_enabled = False 76 | 77 | mt_messages = deque() 78 | 79 | def send_mo_email(): 80 | global lat 81 | global lon 82 | global mo_buffer 83 | global momsn 84 | global mtmsn 85 | global email 86 | global incoming_server 87 | global outgoing_server 88 | global password 89 | global imei 90 | 91 | #put together body 92 | body = \ 93 | 'MOMSN: %d\r\n\ 94 | MTMSN: %d\r\n\ 95 | Time of Session (UTC): %s\r\n\ 96 | Session Status: TRANSFER OK\r\n\ 97 | Message Size: %d\r\n\ 98 | \r\n\ 99 | Unit Location: Lat = %8.6f Long = %8.6f\r\n\ 100 | CEPRadius = 3\r\n\ 101 | \r\n\ 102 | Message is Attached.'\ 103 | % (momsn, mtmsn, time.asctime(), len(mo_buffer), lat, lon) 104 | 105 | #subject 106 | subject = 'SBD Msg From Unit: %d' % (imei) 107 | 108 | #message is included as an attachment 109 | attachment = 'text.sbd' 110 | fd = open(attachment, 'wb') 111 | fd.write(mo_buffer) 112 | fd.close() 113 | 114 | sendMail(subject, body, user, recipient, password, outgoing_server, attachment) 115 | 116 | # def list_serial_ports(): 117 | # # Windows 118 | # if os.name == 'nt': 119 | # # Scan for available ports. 120 | # available = [] 121 | # for i in range(256): 122 | # try: 123 | # s = serial.Serial(i) 124 | # available.append('COM'+str(i + 1)) 125 | # s.close() 126 | # except serial.SerialException: 127 | # pass 128 | # return available 129 | # else: 130 | # # Mac / Linux 131 | # return [port[0] for port in list_ports.comports()] 132 | 133 | def write_text(cmd,start_index): 134 | global mo_set 135 | global mo_buffer 136 | text = cmd[start_index:len(cmd)-1] 137 | mo_buffer = text 138 | mo_set = True 139 | send_ok() 140 | 141 | def sbdi(): 142 | print 'AT+SBDI is not currently supported. Still need to write this function. Use AT+SBDIX instead' 143 | send_error() 144 | 145 | def sbdix(): 146 | global mo_set 147 | global momsn 148 | global mtmsn 149 | global ser 150 | global incoming_server 151 | global user 152 | global password 153 | global imei 154 | global mt_buffer 155 | global mo_ip 156 | global mo_port 157 | global mt_set 158 | 159 | has_incoming_msg = False 160 | received_msg = 0 161 | received_msg_size = 0 162 | unread_msgs = 0 163 | time.sleep(AVERAGE_SBDIX_DELAY) 164 | success = True#(bool(random.getrandbits(1))) 165 | 166 | 167 | if success: 168 | 169 | #use e-mail interface if specified 170 | if email_enabled: 171 | #send e-mail if outgoing data is present 172 | if mo_set and not mo_buffer == "": 173 | if email_enabled: 174 | send_mo_email() 175 | mo_set = False 176 | momsn += 1 177 | 178 | 179 | #check e-mail for messages 180 | temp, received_msg, unread_msgs = checkMessages(incoming_server,user,password,imei) 181 | if received_msg: 182 | #mtmsn += 1 183 | received_msg_size = len(temp) 184 | mt_buffer = temp 185 | mt_set = True 186 | else: 187 | received_msg_size = 0 188 | 189 | elif ip_enabled: 190 | if mo_set and not mo_buffer == "": 191 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 192 | momsn += 1 193 | try: 194 | s.connect((mo_ip, mo_port)) 195 | s.send(assemble_mo_directip_packet(imei, momsn, mtmsn, mo_buffer)) 196 | s.close() 197 | except socket.error as msg: 198 | print "Failed to open {}:{}".format(mo_ip, mo_port) 199 | s.close() 200 | mo_set = False 201 | if len(mt_messages) is not 0: 202 | mtmsn += 1 203 | mt_set = True 204 | mt_buffer = mt_messages.popleft() 205 | unread_msgs = len(mt_messages) 206 | received_msg = mt_set 207 | received_msg_size = len(mt_buffer) 208 | 209 | #TODO: generate result output 210 | if success: rpt = 0 211 | else: rpt = 18 #TODO: add more sophisticated behavior for error msgs 212 | 213 | return_string = "\r\n+SBDIX: %d, %d, %d, %d, %d, %d\r\n" % (rpt,momsn,received_msg,mtmsn,received_msg_size,unread_msgs) 214 | #+SBDIX:,,,,, 215 | print "Sent:",return_string 216 | ser.write(return_string) 217 | send_ok() 218 | 219 | mo_set = False 220 | if received_msg: 221 | mtmsn += 1 222 | def sbd_reg(): 223 | global registered 224 | 225 | success = (bool(random.getrandbits(1))) 226 | if registered == REG_STATUS_REGISTERED: 227 | print 'Already registered' 228 | error_text = ',0' 229 | else: 230 | if success: 231 | registered = REG_STATUS_REGISTERED 232 | error_text = ',0' 233 | else: 234 | registered = REG_STATUS_NOT_REGISTER 235 | error_text = ',17' #TODO: add more sophisticated failures 236 | 237 | ser.write("\nSBDREG:%d%s\r\n" % (registered,error_text)) 238 | send_ok() 239 | 240 | def check_reg_status(): 241 | ser.write("\n+SBDREG:%d\r\n" % (registered)) 242 | send_ok() 243 | 244 | def sbd_det(): 245 | print 'Detached' 246 | registered = True 247 | send_ok() 248 | 249 | def read_text(): 250 | global mt_buffer 251 | ser.write("\n+SBDRT:\r\n%s\r\n" % (mt_buffer)) 252 | send_ok() 253 | 254 | def read_binary(): 255 | global mt_buffer 256 | len_msb = ( len(mt_buffer)/256 ) & 255 257 | len_lsb = ( len(mt_buffer)/1 ) & 255 258 | mt_buffer_sum = sum(bytearray(mt_buffer)) 259 | checksum_msb = ((mt_buffer_sum & (2**16-1)) / (255) ) & 255 260 | checksum_lsb = ((mt_buffer_sum & (2**16-1)) / (1) ) & 255 261 | print "Device is reading binary from MT buffer: ",mt_buffer 262 | #array.array 263 | #ser.write(len_msb) 264 | #ser.write(len_lsb) 265 | #ser.write(mt_buffer) 266 | #ser.write(checksum_msb) 267 | #ser.write(checksum_lsb) 268 | ser.write("%s%s%s%s%s" % (chr(len_msb), chr(len_lsb), mt_buffer,chr(checksum_msb),chr(checksum_lsb)) ) 269 | print "\r\n%s%s%s%s%s" % (chr(len_msb), chr(len_lsb), mt_buffer,chr(checksum_msb),chr(checksum_lsb)) 270 | print checksum_msb, checksum_lsb, len_msb, len_lsb, mt_buffer 271 | send_ok() 272 | 273 | 274 | def send_ok(): 275 | global ser 276 | ser.write('\r\nOK\r\n') 277 | print "Sending OK" 278 | 279 | def send_error(): 280 | global ser 281 | ser.write('\r\nERROR\r\n') 282 | 283 | def send_ready(): 284 | global ser 285 | ser.write('\r\nREADY\r\n') 286 | 287 | def do_ok(): 288 | print 'Received blank command' 289 | send_ok() 290 | 291 | def clear_buffers(buffer): 292 | global mo_set 293 | global mt_set 294 | global mo_buffer 295 | global mt_buffer 296 | 297 | if buffer == 0: 298 | mo_buffer = '' 299 | mo_set = False 300 | ser.write('\r\n0\r\n') 301 | send_ok() 302 | elif buffer == 1: 303 | mt_buffer = '' 304 | mt_set = False 305 | ser.write('\r\n0\r\n') 306 | send_ok() 307 | elif buffer == 2: 308 | mt_buffer = '' 309 | mo_buffer = '' 310 | mo_set = False 311 | mt_set = False 312 | ser.write('\r\n0\r\n') 313 | send_ok() 314 | else: 315 | send_error() 316 | 317 | 318 | def clear_momsn(): 319 | momsn = 0 320 | ser.write('\r\n0\r\n') 321 | 322 | def get_sbd_status(): 323 | global mt_set 324 | global mo_set 325 | global momsn 326 | global mtmsn 327 | 328 | if mt_set: 329 | mt_flag = 1 330 | else: 331 | mt_flag = 0 332 | 333 | if mo_set: 334 | mo_flag = 1 335 | else: 336 | mo_flag = 0 337 | 338 | if mt_set: 339 | reported_mtmsn = mtmsn 340 | else: 341 | reported_mtmsn = -1 342 | 343 | 344 | return_string = "\nSBDS:%d,%d,%d,%d\r\n" % (mo_flag, momsn, mt_flag, mtmsn) 345 | 346 | ser.write(return_string) 347 | send_ok() 348 | 349 | def copy_mo_to_mt(): 350 | global mo_buffer 351 | global mt_buffer 352 | 353 | mt_buffer = mo_buffer 354 | 355 | return_string = "\nSBDTC: Outbound SBD Copied to Inbound SBD: size = %d\r\n" % (len(mo_buffer)) 356 | ser.write(return_string) 357 | 358 | send_ok() 359 | 360 | def which_gateway(): 361 | return_string = "\rSBDGW:EMSS\r\n" 362 | 363 | ser.write(return_string) 364 | send_ok() 365 | 366 | def get_system_time(): 367 | return_string = "\r\n---MSSTM: 01002000\r\n" 368 | ser.write(return_string) 369 | send_ok() 370 | print 'We havent actually implemented MSSTM this yet.' 371 | 372 | def set_ring_indicator(cmd,start_index): 373 | global ring_enable 374 | 375 | text = cmd[start_index:len(cmd)-1] 376 | 377 | if len(text) == 1: 378 | if text[0] == '0': 379 | send_ok() 380 | elif text[0] == '1': 381 | send_ok() 382 | else: 383 | send_error() 384 | else: 385 | send_error() 386 | 387 | 388 | def get_signal_strength(): 389 | return_string = "\r\n+CSQ:%d\r\n" % 5#(random.randint(0,5)) 390 | time.sleep(AVERAGE_SBDIX_DELAY) 391 | ser.write(return_string) 392 | send_ok() 393 | 394 | def get_valid_rssi(): 395 | return_string = "\n+CSQ:(0-5)\r\n" 396 | ser.write(return_string) 397 | send_ok() 398 | 399 | def get_lock_status(): 400 | global locked 401 | 402 | return_string = "\n+CULK:%d\r\n" % ( locked ) 403 | ser.write(return_string) 404 | send_ok() 405 | 406 | def get_manufacturer(): 407 | return_string = "\n+Iridium\r\n" 408 | ser.write(return_string) 409 | send_ok() 410 | 411 | def get_model(): 412 | return_string = "\nIRIDIUM 9600 Family SBD Transceiver\r\n" 413 | ser.write(return_string) 414 | send_ok() 415 | 416 | def get_gsn(): 417 | return_string = "\n300234060604220\r\n" 418 | ser.write(return_string) 419 | send_ok() 420 | 421 | def get_gmr(): 422 | return_string = "\n3Call Processor Version: Long string\r\n" 423 | print 'Warning: get_gmr function not fully implemented' 424 | ser.write(return_string) 425 | send_ok() 426 | 427 | def write_binary_start(cmd,start_index): 428 | global binary_rx_incoming_bytes 429 | global binary_rx 430 | 431 | text = cmd[start_index:len(cmd)-1] 432 | try: 433 | binary_rx_incoming_bytes = int(text) 434 | if (binary_rx_incoming_bytes > 340): 435 | ser.write('\r\r\n3\r\n') 436 | send_ok() 437 | binary_rx_incoming_bytes = 0 438 | else: 439 | print 'Ready to receive {} bytes'.format(binary_rx_incoming_bytes) 440 | send_ready() 441 | binary_rx = True 442 | except: 443 | send_error() 444 | 445 | def parse_cmd(cmd): 446 | global echo 447 | #get string up to newline or '=' 448 | index = cmd.find('=') 449 | if index == -1: 450 | index = cmd.find('\r') 451 | cmd_type = cmd[0:index].lower() 452 | 453 | #print cmd_type 454 | 455 | if cmd_type == 'at' : do_ok() 456 | elif cmd_type == 'at+csq' : get_signal_strength() 457 | elif cmd_type == 'at+csq=?' : get_valid_rssi() 458 | elif cmd_type == 'at+culk?' : get_lock_status() 459 | elif cmd_type == 'at+gmi' : get_manufacturer() 460 | elif cmd_type == 'at+gmm' : get_model() 461 | elif cmd_type == 'at+gsn' : get_gsn() 462 | elif cmd_type == 'at+gmr' : get_gmr() 463 | elif cmd_type == 'at+sbdwt' : write_text(cmd,index + 1) 464 | elif cmd_type == 'at+sbdwb' : write_binary_start(cmd,index + 1) 465 | elif cmd_type == 'at+sbdi' : sbdi() 466 | elif cmd_type == 'at+sbdix' : sbdix() 467 | elif cmd_type == 'at+sbdreg' : sbd_reg() 468 | elif cmd_type == 'at+sbdreg?' : check_reg_status() 469 | elif cmd_type == 'at+sbddet' : sbd_det() 470 | elif cmd_type == 'at+sbdrt' : read_text() 471 | elif cmd_type == 'at+sbdrb' : read_binary() 472 | elif cmd_type == 'at+sbdd0' : clear_buffers(0) 473 | elif cmd_type == 'at+sbdd1' : clear_buffers(1) 474 | elif cmd_type == 'at+sbdd2' : clear_buffers(2) 475 | elif cmd_type == 'at+sbdc' : clear_momsn() 476 | elif cmd_type == 'at+sbds' : get_sbd_status() 477 | elif cmd_type == 'at+sbdtc' : copy_mo_to_mt() 478 | elif cmd_type == 'at+sbdgw' : which_gateway() 479 | elif cmd_type == 'at-msstm' : get_system_time() 480 | elif cmd_type == 'at+sbdmta' : set_ring_indicator(cmd,index + 1) 481 | elif cmd_type == 'ate0' or cmd_type == 'ate': 482 | echo = False 483 | do_ok() 484 | elif cmd_type == 'ate1' : do_ok() 485 | elif cmd_type == 'at&d0' : do_ok() 486 | elif cmd_type == 'at&k0' : do_ok() 487 | else : send_error() 488 | 489 | 490 | def open_port(dev,baudrate): 491 | ser = serial.Serial(dev, 19200, timeout=.1, parity=serial.PARITY_NONE) 492 | return ser 493 | 494 | class MobileTerminatedHandler(asyncore.dispatcher_with_send): 495 | def __init__(self, sock, addr): 496 | asyncore.dispatcher_with_send.__init__(self, sock) 497 | self.client = None 498 | self.addr = addr 499 | self.data = "" 500 | self.msg_length = 0 501 | self.preheader_fmt = '!bH' 502 | self.preheader_size = struct.calcsize(self.preheader_fmt) 503 | 504 | def handle_read(self): 505 | global mt_messages 506 | 507 | if len(self.data) < self.preheader_size: 508 | self.data += self.recv(self.preheader_size) 509 | preheader = struct.unpack(self.preheader_fmt, self.data) 510 | self.msg_length = preheader[1] 511 | else: 512 | self.data += self.recv(self.msg_length) 513 | 514 | print self.msg_length 515 | print self.data.encode("hex") 516 | 517 | if len(self.data) >= self.msg_length: 518 | mt_packet = None 519 | try: 520 | mt_packet = parse_mt_directip_packet(self.data, mt_messages) 521 | except: 522 | print 'MT Handler: Invalid message' 523 | # response message 524 | self.send(assemble_mt_directip_response(mt_packet, mt_messages)) 525 | self.handle_close() 526 | 527 | def handle_close(self): 528 | print 'MT Handler: Connection closed from %s' % repr(self.addr) 529 | sys.stdout.flush() 530 | self.close() 531 | 532 | 533 | class MobileTerminatedServer(asyncore.dispatcher): 534 | 535 | def __init__(self, host, port): 536 | asyncore.dispatcher.__init__(self) 537 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 538 | self.set_reuse_addr() 539 | self.bind((host, port)) 540 | self.listen(5) 541 | 542 | def handle_accept(self): 543 | pair = self.accept() 544 | if pair is not None: 545 | sock, addr = pair 546 | print 'MT Handler: Incoming connection from %s' % repr(addr) 547 | sys.stdout.flush() 548 | try: 549 | handler = MobileTerminatedHandler(sock, addr) 550 | except: 551 | print "MT Handler: Unexpected error:", sys.exc_info()[0] 552 | 553 | 554 | 555 | 556 | def main(): 557 | 558 | global ser 559 | global mo_buffer 560 | global mo_set 561 | global binary_rx_incoming_bytes 562 | global binary_rx 563 | 564 | global user 565 | global recipient 566 | global incoming_server 567 | global outgoing_server 568 | global password 569 | 570 | global email_enabled 571 | global ip_enabled 572 | global http_post_enabled 573 | 574 | global mo_ip 575 | global mo_port 576 | global mt_port 577 | 578 | global echo 579 | 580 | 581 | parser = OptionParser() 582 | parser.add_option("-d", "--dev", dest="dev", action="store", help="tty dev(ex. '/dev/ttyUSB0'", metavar="DEV") 583 | parser.add_option("-p", "--passwd", dest="passwd", action="store", help="Password", metavar="PASSWD") 584 | parser.add_option("-u", "--user", dest="user", action="store", help="E-mail account username", metavar="USER") 585 | parser.add_option("-r", "--recipient", dest="recipient", action="store", help="Destination e-mail address.", metavar="USER") 586 | parser.add_option("-i", "--in_srv", dest="in_srv", action="store", help="Incoming e-mail server url", metavar="IN_SRV") 587 | parser.add_option("-o", "--out_srv", dest="out_srv", action="store", help="Outging e-mail server", metavar="OUT_SRV") 588 | parser.add_option("--mo_ip", dest="mo_ip", action="store", help="Mobile-originated DirectIP server IP address", metavar="MO_IP", default="127.0.0.1") 589 | parser.add_option("--mo_port", dest="mo_port", action="store", help="Mobile-originated DirectIP server Port", metavar="MO_PORT", default=10801) 590 | parser.add_option("--mt_port", dest="mt_port", action="store", help="Mobile-terminated DirectIP server Port", metavar="MT_PORT", default=10800) 591 | parser.add_option("-m", "--mode", dest="mode", action="store", help="Mode: EMAIL,HTTP_POST,IP,NONE", default="NONE", metavar="MODE") 592 | parser.add_option("-e", "--imei", dest="imei", action="store", help="IMEI for this modem", default="300234060379270", metavar="MODE") 593 | 594 | (options, args) = parser.parse_args() 595 | 596 | mt_port = int(options.mt_port) 597 | 598 | #check for valid arguments 599 | if options.mode == "EMAIL": 600 | if options.passwd is None or options.user is None or options.recipient is None or options.in_srv is None or options.out_srv is None: 601 | print 'If you want to use e-mail, you must specify in/out servers, user, password, and recipient address.' 602 | sys.exit() 603 | else: 604 | email_enabled = True 605 | elif options.mode == "HTTP_POST": 606 | print 'Not implemented yet' 607 | sys.exit() 608 | elif options.mode == "IP": 609 | print 'Using IP mode with MO ({}:{}) and MT (0.0.0.0:{}) servers'.format(options.mo_ip, int(options.mo_port), options.mt_port) 610 | server = MobileTerminatedServer('0.0.0.0', mt_port) 611 | print "Started MT Server on port {}".format(mt_port) 612 | sys.stdout.flush() 613 | ip_enabled = True 614 | else: 615 | print "No valid mode specified" 616 | sys.exit() 617 | 618 | 619 | user = options.user 620 | recipient = options.recipient 621 | incoming_server = options.in_srv 622 | outgoing_server = options.out_srv 623 | password = options.passwd 624 | 625 | mo_ip = options.mo_ip 626 | mo_port = int(options.mo_port) 627 | imei = options.imei 628 | 629 | now_get_checksum_first = False 630 | now_get_checksum_second = False 631 | 632 | try: 633 | ser = open_port(options.dev,19200) 634 | except: 635 | print "Could not open serial port. Exiting." 636 | # print "FYI - Here's a list of ports on your system." 637 | # print list_serial_ports() 638 | sys.exit() 639 | 640 | rx_buffer = '' 641 | 642 | binary_checksum = 0 643 | 644 | while(1): 645 | if ip_enabled: 646 | asyncore.loop(timeout=0, count=1) # non-blocking loop 647 | 648 | new_char = ser.read() # timeout after .1 seconds to return to asyncore.loop() 649 | if (len(new_char) == 0): 650 | continue 651 | 652 | #print new_char 653 | if echo and not binary_rx: 654 | ser.write(new_char) 655 | 656 | if not binary_rx: 657 | rx_buffer = rx_buffer + new_char 658 | #look for eol char, #TODO figure out what is really devined as EOL for iridium modem 659 | if new_char == chr(EOL_CHAR): 660 | if(len(rx_buffer) > 2): 661 | print "Here is what I received:%s" % (rx_buffer) 662 | parse_cmd(rx_buffer) 663 | rx_buffer = '' 664 | else: 665 | rx_buffer = '' 666 | 667 | #process backspace 668 | elif new_char == chr(BACKSPACE_CHAR) and not binary_rx: 669 | rx_buffer[:len(rx_buffer)-1] 670 | rx_buffer = rx_buffer[:len(rx_buffer)-2] #remove char if backspace 671 | else: 672 | if now_get_checksum_first: 673 | checksum_first = ord(new_char) 674 | now_get_checksum_first = False 675 | now_get_checksum_second = True 676 | elif now_get_checksum_second: 677 | checksum_second = ord(new_char) 678 | now_get_checksum_first = False 679 | now_get_checksum_second = False 680 | #check the checksum 681 | if (checksum_first * 256 + checksum_second) == (binary_checksum & (2**16-1)): 682 | print "Good binary checksum" 683 | ser.write('\r\n0\r\n') 684 | send_ok() 685 | mo_buffer = rx_buffer 686 | rx_buffer = '' 687 | mo_set = True 688 | else: 689 | print "Bad binary checksum" 690 | ser.write('\r\n2\r\n') 691 | send_ok() 692 | rx_buffer = '' 693 | ser.write('\n') 694 | binary_checksum = 0 695 | binary_rx = False 696 | else: 697 | if binary_rx_incoming_bytes == 1: 698 | now_get_checksum_first = True 699 | binary_checksum = binary_checksum + ord(new_char) 700 | rx_buffer = rx_buffer + new_char 701 | else: 702 | binary_rx_incoming_bytes -= 1 703 | rx_buffer = rx_buffer + new_char 704 | binary_checksum = binary_checksum + ord(new_char) 705 | 706 | 707 | 708 | if __name__ == '__main__': 709 | main() 710 | -------------------------------------------------------------------------------- /python/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenUAS/virtual_iridium/ed613f59d4adb48cc63a270eeedc8a3f7be76adc/python/__init__.py -------------------------------------------------------------------------------- /python/imap_stuff.py: -------------------------------------------------------------------------------- 1 | 2 | #v+ 3 | #!/usr/bin/env python 4 | 5 | import imaplib 6 | import email 7 | import imaplib 8 | 9 | def checkMessages(incoming_server, user, password,imsi): 10 | has_data = False 11 | obj = imaplib.IMAP4_SSL(incoming_server, '993') 12 | obj.login(user, password) 13 | obj.select('Inbox') 14 | typ ,data = obj.search(None,'UnSeen') 15 | string = data[0] 16 | msg_count = len(string.split(' ')) 17 | index = string.find(' ') 18 | if not index == -1: 19 | string = string[:string.find(' ')] 20 | if(msg_count >= 1 and len(string) > 0): 21 | obj.store(string,'+FLAGS','\Seen') 22 | typ, data = obj.fetch(string, '(RFC822)') 23 | 24 | #TODO: filter by imei subject 25 | 26 | #get attachment 27 | text = data[0][1] 28 | msg = email.message_from_string(text) 29 | for part in msg.walk(): 30 | if part.get_content_maintype() == 'multipart': 31 | continue 32 | if part.get('Content-Disposition') is None: 33 | continue 34 | filename = part.get_filename() 35 | data = part.get_payload(decode=True) 36 | if not data: 37 | has_data = False 38 | data = [] 39 | continue 40 | has_data = True 41 | 42 | return data, has_data, max(msg_count - 1,0) 43 | 44 | #def main(): 45 | # checkMessages('imap.gmail.com','jmalsbury.personal@gmail.com','sweet525',0) 46 | 47 | #if __name__ == '__main__': 48 | # main() 49 | -------------------------------------------------------------------------------- /python/iridium_mo_forward_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # splits traffic to Hayes Modem emulator and to an Iridium9602 simulator 4 | 5 | import asyncore 6 | import socket 7 | from optparse import OptionParser 8 | 9 | 10 | class ConditionalForwardClient(asyncore.dispatcher_with_send): 11 | 12 | def __init__(self, server, host, port): 13 | asyncore.dispatcher_with_send.__init__(self) 14 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 15 | self.connect( (host, port) ) 16 | self.server = server 17 | 18 | def handle_read(self): 19 | data = self.recv(64) 20 | if data: 21 | self.server.send(data) 22 | 23 | 24 | class ConditionalForwardHandler(asyncore.dispatcher_with_send): 25 | 26 | def __init__(self, sock, addr): 27 | asyncore.dispatcher_with_send.__init__(self, sock) 28 | self.identified_protocol = False 29 | self.addr = addr 30 | self.initial_data = "" 31 | self.buf = "" 32 | self.hayes_client = ConditionalForwardClient(self, options.hayes_server, int(options.hayes_port)) 33 | self.sbd_client = ConditionalForwardClient(self, options.sbd_server, int(options.sbd_port)) 34 | self.sbd_write = False 35 | self.sbd_bytes_remaining = 0 36 | 37 | def handle_read(self): 38 | data = self.recv(256) 39 | 40 | print data.encode("hex") 41 | 42 | if not data: 43 | return 44 | elif self.sbd_write: # not line mode - raw data 45 | self.sbd_send_bytes(data) 46 | elif data == "+++": 47 | self.hayes_client.send(data) 48 | else: # line based Command data 49 | self.buf += data 50 | line_list = self.buf.split('\r') 51 | # partial line 52 | self.buf = line_list[-1] 53 | 54 | for line in line_list[0:-1]: 55 | self.line_process(line) 56 | 57 | def handle_close(self): 58 | print 'Connection closed from %s' % repr(self.addr) 59 | sys.stdout.flush() 60 | self.close() 61 | 62 | def line_process(self, line): 63 | line_cr = line + '\r' 64 | 65 | if line.strip().upper() in ['ATE']: 66 | self.hayes_client.send(line_cr) 67 | self.sbd_client.send(line_cr) 68 | else: 69 | if len(line) >= 6 and line[2:6].upper() == "+SBD": 70 | self.sbd_client.send(line_cr) 71 | if len(line) >= 8 and line[2:8].upper() == "+SBDWB": 72 | parts = line.split('=') 73 | self.sbd_bytes_remaining = int(parts[1]) + 2 # 2 checksum bytes 74 | self.sbd_write = True 75 | else: 76 | self.hayes_client.send(line_cr) 77 | 78 | def sbd_send_bytes(self, bytes): 79 | self.sbd_bytes_remaining -= len(bytes) 80 | self.sbd_client.send(bytes) 81 | print self.sbd_bytes_remaining 82 | if self.sbd_bytes_remaining <= 0: 83 | self.sbd_write = False 84 | 85 | class ConditionalForwardServer(asyncore.dispatcher): 86 | 87 | def __init__(self, host, port): 88 | asyncore.dispatcher.__init__(self) 89 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 90 | self.set_reuse_addr() 91 | self.bind((host, port)) 92 | self.listen(5) 93 | 94 | def handle_accept(self): 95 | pair = self.accept() 96 | if pair is not None: 97 | sock, addr = pair 98 | print 'Incoming connection from %s' % repr(addr) 99 | sys.stdout.flush() 100 | try: 101 | handler = ConditionalForwardHandler(sock, addr) 102 | except: 103 | print "Unexpected error:", sys.exc_info()[0] 104 | 105 | import sys 106 | 107 | 108 | 109 | 110 | 111 | parser = OptionParser() 112 | parser.add_option("-p", "--port", dest="port", action="store", help="bind port", default=4010) 113 | parser.add_option("-a", "--hayes_address", dest="hayes_server", action="store", help="address to connect to Hayes AT emulator", default="127.0.0.1") 114 | parser.add_option("-b", "--hayes_port", dest="hayes_port", action="store", help="address to connect to Hayes AT emulator", default=4001) 115 | parser.add_option("-c", "--sbd_address", dest="sbd_server", action="store", help="address to connect to SBD emulator", default="127.0.0.1") 116 | parser.add_option("-d", "--sbd_port", dest="sbd_port", action="store", help="address to connect to SBD emulator", default=4020) 117 | 118 | (options, args) = parser.parse_args() 119 | 120 | 121 | forward_address = '0.0.0.0' 122 | 123 | print "Iridium Port forwarder starting up ..." 124 | print "Listening on port: {}".format(int(options.port)) 125 | print "Connecting for Iridium9602 SBD on %s:%d" % (options.sbd_server, int(options.sbd_port)) 126 | print "Connecting for Hayes (ATDuck) on %s:%d" % (options.hayes_server, int(options.hayes_port)) 127 | sys.stdout.flush() 128 | 129 | server = ConditionalForwardServer(forward_address, int(options.port)) 130 | asyncore.loop() 131 | -------------------------------------------------------------------------------- /python/iridium_mt_forward_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Handles incoming Iridium SBD traffic and port forwards as appropriate based on IMEI 4 | # Used to allow you to run multiple Iridium9602 simulator instances that can be handled 5 | # by a single MT DirectIP server 6 | 7 | import asyncore 8 | import socket 9 | from virtual_iridium.sbd_packets import parse_mt_directip_packet 10 | from collections import deque 11 | import struct 12 | 13 | # this script listens (binds) on this port 14 | mt_sbd_address = '0.0.0.0' 15 | mt_sbd_port = 40002 16 | 17 | # maps imei to address and port 18 | forward_address = { "300234060379270" : ("127.0.0.1",40010), "300234060379271" : ("127.0.0.1",40011), "300234060379272" : ("127.0.0.1",40012), "300234060379273" : ("127.0.0.1",40013), "300234060379274" : ("127.0.0.1",40014) } 19 | 20 | class ConditionalSBDForwardClient(asyncore.dispatcher_with_send): 21 | 22 | def __init__(self, server, host, port): 23 | asyncore.dispatcher_with_send.__init__(self) 24 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 25 | self.connect( (host, port) ) 26 | self.server = server 27 | 28 | def handle_read(self): 29 | data = self.recv(64) 30 | if data: 31 | self.server.send(data) 32 | 33 | 34 | class ConditionalSBDForwardHandler(asyncore.dispatcher_with_send): 35 | 36 | def __init__(self, sock, addr): 37 | asyncore.dispatcher_with_send.__init__(self, sock) 38 | self.identified_protocol = False 39 | self.client = None 40 | self.addr = addr 41 | self.data = '' 42 | self.preheader_fmt = '!bH' 43 | self.preheader_size = struct.calcsize(self.preheader_fmt) 44 | 45 | def handle_read(self): 46 | if len(self.data) < self.preheader_size: 47 | self.data += self.recv(self.preheader_size) 48 | if not self.data: 49 | return 50 | preheader = struct.unpack(self.preheader_fmt, self.data) 51 | self.msg_length = preheader[1] 52 | else: 53 | self.data += self.recv(self.msg_length) 54 | 55 | print self.msg_length 56 | print self.data.encode("hex") 57 | 58 | if len(self.data) >= self.msg_length: 59 | mt_packet = None 60 | mt_messages = deque() 61 | try: 62 | mt_packet = parse_mt_directip_packet(self.data, mt_messages) 63 | except: 64 | print 'MT Handler: Invalid message' 65 | sys.stdout.flush() 66 | 67 | imei = mt_packet[0][1] 68 | 69 | print 'Attempting to forward message for imei: {}' .format(imei) 70 | 71 | if forward_address.has_key(imei): 72 | self.client = ConditionalSBDForwardClient(self, forward_address[imei][0], forward_address[imei][1]) 73 | self.client.send(self.data) 74 | self.data = '' 75 | else: 76 | print 'No forwarding set up for imei: {}'.format(imei) 77 | self.close() 78 | 79 | def handle_close(self): 80 | print 'Connection closed from %s' % repr(self.addr) 81 | sys.stdout.flush() 82 | if self.client is not None: 83 | self.client.close() 84 | self.close() 85 | 86 | 87 | class ConditionalSBDForwardServer(asyncore.dispatcher): 88 | 89 | def __init__(self, host, port): 90 | asyncore.dispatcher.__init__(self) 91 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 92 | self.set_reuse_addr() 93 | self.bind((host, port)) 94 | self.listen(5) 95 | 96 | def handle_accept(self): 97 | pair = self.accept() 98 | if pair is not None: 99 | sock, addr = pair 100 | print 'Incoming connection from %s' % repr(addr) 101 | sys.stdout.flush() 102 | try: 103 | handler = ConditionalSBDForwardHandler(sock, addr) 104 | except: 105 | print "Unexpected error:", sys.exc_info()[0] 106 | 107 | import sys 108 | print "Iridium SBD Port forwarder starting up ..." 109 | print "Listening for SBD on port: %d" % mt_sbd_port 110 | 111 | 112 | sys.stdout.flush() 113 | 114 | sbd_server = ConditionalSBDForwardServer(mt_sbd_address, mt_sbd_port) 115 | asyncore.loop() 116 | -------------------------------------------------------------------------------- /python/iridium_rudics_shore_connection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from optparse import OptionParser 3 | import socket 4 | import time 5 | 6 | def close_hayes_socket(hayes_socket): 7 | hayes_socket.send("+++"); 8 | time.sleep(2) 9 | hayes_socket.send("ATH\r\n"); 10 | 11 | 12 | def main(): 13 | parser = OptionParser() 14 | parser.add_option("-a", "--hayes_address", dest="hayes_address", action="store", help="Hayes Simulator Address") 15 | parser.add_option("-p", "--hayes_port", dest="hayes_port", action="store", help="Hayes Simulator Port") 16 | 17 | parser.add_option("-A", "--shore_address", dest="shore_address", action="store", help="Shore driver Address") 18 | parser.add_option("-P", "--shore_port", dest="shore_port", action="store", help="Shore driver Port") 19 | 20 | (options, args) = parser.parse_args() 21 | print options 22 | 23 | connected = False 24 | buffer_size = 1024 25 | 26 | shore_socket = None; 27 | 28 | 29 | hayes_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 30 | hayes_socket.connect((options.hayes_address, int(options.hayes_port))) 31 | hayes_socket.settimeout(0.1) 32 | hayes_socket.send("OK") 33 | while(1): 34 | try: 35 | if connected: 36 | try: 37 | shore_data = shore_socket.recv(buffer_size) 38 | if(len(shore_data) > 0): 39 | hayes_socket.send(shore_data) 40 | else: 41 | print "Zero read" 42 | connected = False 43 | close_hayes_socket(hayes_socket) 44 | except socket.timeout: 45 | pass 46 | except socket.error as e: 47 | print "Shore socket error: ",e 48 | connected = False 49 | close_hayes_socket(hayes_socket, connected) 50 | 51 | data = hayes_socket.recv(buffer_size) 52 | print data 53 | 54 | if("NO CARRIER" in data): 55 | print "Disconnected!" 56 | connected = False 57 | shore_socket.close() 58 | 59 | if connected and len(data) > 0: 60 | try: 61 | print "To Shore: ",data.encode("hex") 62 | shore_socket.send(data) 63 | except socket.timeout: 64 | print "Timeout sending data" 65 | except socket.error as e: 66 | print "Shore socket error: ",e 67 | connected = False 68 | hayes_socket.send("+++"); 69 | time.sleep(2) 70 | hayes_socket.send("ATH\r\n"); 71 | 72 | if(data.strip() == "RING"): 73 | hayes_socket.send("ATA\r\n"); 74 | elif("CONNECT" in data): 75 | print "Connected!" 76 | shore_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 77 | shore_socket.settimeout(0.1) 78 | shore_socket.connect((options.shore_address, int(options.shore_port))) 79 | connected = True 80 | 81 | except socket.timeout: 82 | time.sleep(0.01) 83 | except socket.error as e: 84 | print e 85 | 86 | 87 | if __name__ == '__main__': 88 | main() 89 | -------------------------------------------------------------------------------- /python/sbd_packets.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import random 3 | import time 4 | 5 | def assemble_mo_directip_packet(imei, momsn, mtmsn, mo_buffer): 6 | 7 | # ==== MO HEADER ==== 8 | # MO Header IEI char 0x01 9 | # MO Header Length unsigned short 10 | # CDR Reference (Auto ID) unsigned integer 11 | # IMEI char[] (15 bytes) 12 | # Session Status unsigned char 13 | # MOMSN unsigned short 14 | # MTMSN unsigned short 15 | # Time of Session unsigned integer 16 | header_fmt = "!bHI15sBHHI" 17 | header_iei = 0x01 18 | # header_length does not include iei and length fields 19 | header_length = struct.calcsize(header_fmt) - struct.calcsize('!bH') 20 | cdr_ref = random.getrandbits(32) 21 | session_status = 0 22 | header = struct.pack(header_fmt, header_iei, header_length, cdr_ref, str(imei), session_status, momsn, mtmsn, int(time.time())) 23 | 24 | # ==== MO PAYLOAD ==== 25 | # MO Payload IEI char 0x02 26 | # MO Payload Length unsigned short 27 | # MO Payload char 28 | payload_iei = 0x02 29 | payload_length = min(len(mo_buffer), 1960) 30 | payload = struct.pack('!bH' + str(payload_length) + 's', payload_iei, payload_length, mo_buffer) 31 | 32 | protocol_rev_no = 1 33 | overall_msg_length = len(header) + len(payload) 34 | preheader = struct.pack('!bH', protocol_rev_no, overall_msg_length) 35 | return preheader + header + payload 36 | 37 | def parse_mt_directip_packet(buffer, mt_messages): 38 | 39 | parse_offset = 0 40 | preheader_fmt = '!bH' 41 | ie_header_fmt = '!bH' 42 | preheader = struct.unpack_from(preheader_fmt, buffer, parse_offset) 43 | parse_offset += struct.calcsize(preheader_fmt) 44 | 45 | header_iei = 0x41 46 | payload_iei = 0x42 47 | prio_iei = 0x46 48 | header = None 49 | payload = None 50 | 51 | while parse_offset + struct.calcsize(ie_header_fmt) < len(buffer): 52 | ie_header = struct.unpack_from(ie_header_fmt, buffer, parse_offset) 53 | print 'IE Header: ' + str(ie_header) 54 | parse_offset += struct.calcsize(ie_header_fmt) 55 | 56 | if ie_header[0] == header_iei: 57 | # ==== MT HEADER ==== 58 | # MT Header IEI char 0x41 59 | # MT Header Length unsigned short 60 | # Unique Client Message ID unsigned int 61 | # IMEI (User ID) char[] (15 bytes) 62 | # MT Disposition Flags char unsigned short 63 | header_fmt = '!I15sH' 64 | header = struct.unpack_from(header_fmt, buffer, parse_offset) 65 | print 'Header: ' + str(header) 66 | elif ie_header[0] == payload_iei: 67 | # ==== MT PAYLOAD ==== 68 | # MO Payload IEI char 2 69 | # MO Payload Length unsigned short 70 | # MO Payload char 71 | payload_fmt = str(ie_header[1]) + 's' 72 | payload = struct.unpack_from(payload_fmt, buffer, parse_offset) 73 | print 'Payload: ' + str(payload) 74 | mt_messages.append(payload[0]) 75 | else: 76 | print 'Unknown IEI: %x'.format(ie_header[0]) 77 | 78 | parse_offset += ie_header[1] 79 | return (header, payload) 80 | 81 | def assemble_mt_directip_response(mt_packet, mt_messages): 82 | 83 | confirm_iei = 0x44 84 | confirm_fmt = "!bHI15sIh" 85 | confirm_length = struct.calcsize(confirm_fmt) - struct.calcsize('!bH') 86 | session_status = 0 87 | client_id = 0 88 | imei = '0'*15 89 | if mt_packet[0] is not None: 90 | print mt_packet 91 | session_status = len(mt_messages) 92 | client_id = mt_packet[0][0] 93 | imei = mt_packet[0][1] 94 | else: 95 | session_status = -7 # violation of MT DirectIP protocol error 96 | 97 | 98 | # MT Confirmation Message IEI 99 | # MT Confirmation Message Length 100 | # Unique Client Message ID 101 | # IMEI (User ID) 102 | # Auto ID Reference 103 | # MT Message Status 104 | auto_id = random.getrandbits(32) 105 | confirm = struct.pack(confirm_fmt, confirm_iei, confirm_length, client_id, imei, auto_id, session_status) 106 | 107 | protocol_rev_no = 1 108 | overall_msg_length = len(confirm) 109 | preheader = struct.pack('!bH', protocol_rev_no, overall_msg_length) 110 | return preheader + confirm 111 | -------------------------------------------------------------------------------- /python/smtp_stuff.py: -------------------------------------------------------------------------------- 1 | import os 2 | import smtplib 3 | import mimetypes 4 | from email.MIMEMultipart import MIMEMultipart 5 | from email.MIMEBase import MIMEBase 6 | from email.MIMEText import MIMEText 7 | from email.MIMEAudio import MIMEAudio 8 | from email.MIMEImage import MIMEImage 9 | from email.Encoders import encode_base64 10 | 11 | def sendMail(subject, text, user, recipient, password, smtp_server, attachmentFilePath): 12 | gmailUser = user 13 | gmailPassword = password 14 | 15 | msg = MIMEMultipart() 16 | msg['From'] = 'sbdservice@sbd.iridium.com' 17 | msg['To'] = recipient 18 | msg['Subject'] = subject 19 | msg.attach(MIMEText(text)) 20 | 21 | 22 | msg.attach(getAttachment(attachmentFilePath)) 23 | 24 | mailServer = smtplib.SMTP(smtp_server, 587) 25 | mailServer.ehlo() 26 | mailServer.starttls() 27 | mailServer.ehlo() 28 | mailServer.login(gmailUser, gmailPassword) 29 | mailServer.sendmail(gmailUser, recipient, msg.as_string()) 30 | mailServer.close() 31 | 32 | print('Sent email to %s' % recipient) 33 | 34 | def getAttachment(attachmentFilePath): 35 | contentType, encoding = mimetypes.guess_type(attachmentFilePath) 36 | 37 | if contentType is None or encoding is not None: 38 | contentType = 'application/octet-stream' 39 | 40 | mainType, subType = contentType.split('/', 1) 41 | file = open(attachmentFilePath, 'rb') 42 | 43 | if mainType == 'text': 44 | attachment = MIMEText(file.read()) 45 | elif mainType == 'message': 46 | attachment = email.message_from_file(file) 47 | elif mainType == 'image': 48 | attachment = MIMEImage(file.read(),_subType=subType) 49 | elif mainType == 'audio': 50 | attachment = MIMEAudio(file.read(),_subType=subType) 51 | else: 52 | attachment = MIMEBase(mainType, subType) 53 | attachment.set_payload(file.read()) 54 | encode_base64(attachment) 55 | 56 | file.close() 57 | 58 | attachment.add_header('Content-Disposition', 'attachment', filename=os.path.basename(attachmentFilePath)) 59 | return attachment 60 | 61 | 62 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | setup(name='virtual_iridium', 3 | version='1.0', 4 | package_dir={'virtual_iridium': 'python'}, 5 | packages=['virtual_iridium'], 6 | scripts=['python/Iridium9602.py', 'python/iridium_mo_forward_server.py', 'python/iridium_mt_forward_server.py', 'python/iridium_rudics_shore_connection.py'] 7 | ) 8 | --------------------------------------------------------------------------------