[Linux] Beagleboard GPS SMS Cellphone Module ready!

This board i made for the Space Camera Live project, as a backup GPS system, that can also send SMS messages and make some calls. Most importantly, it can get python scripts loaded to it. For instance, you can call it up (it takes a sim card) – then ofcourse it will not pick it up, but it will send you back an SMS message immediatelly containing its GPS coordinates!
This module is spaced such that it can be hooked up to the Beagle Board easily, it has the same hole dimensions and sizes as the board. Also, ofcourse, this board can be used with any machine that has USB (every machine); so it can also be used as an evaluation board. Not bad for just $3 production costs. If you want a bare PCB board (this white one) you can have it, i’ll send it over regular email.

Prototype without flaws, works 100%

This prototype worked out of the box. I had the PCB’s in, soldered them, plugged it in the computer, hey presto: worked.

Module design, schematics, internals, etc

I designed this module myself. You can see everything about how it i had it produced here: LINK.

Rembrandt is pleased

As i had some empty space, i decided to let an etch of rembrandt be etched on the bottom of the PCB. Worked out great.

Bottom of the module

Short Introduction Video

Pictures!

Module with the GPRS and GPS antenna's on the left, USB on the right.

Close-up

This modules takes python scripts

This module can operate autonomously if you give it a python script.
Here below is a script, that, when loaded to the module, sends anyone who textmessages “position” to the module, the GPS position back. Also, there is a built in function that resets the module when it fails when it reaches a GPS altitude of 24km.
There are two files: run.py and main.py. Download RSTERM.exe on windows. Make sure you set the baudrate to 115200 In the Python menu, choose your folder with these scripts. Press compile, then upload the two .pyo files. Then, set run.py as the active script, then select the boot after 10 seconds mode, then reboot and voila you’re done, the script will automatically load.

If you now send an sms to the module, i get an sms back with the GPS position!
WATCH OUT: When i was editing the code, it would NOT work if i included too many spaces or tabs (=indentations). So try to avoid those and just write your code on one level.

run.py

  1. import main

main.py

  1. # Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
  2. # THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS
  3. # PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR
  4. # OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS
  5. # LICENSE OR COPYRIGHT LAW IS PROHIBITED.
  6. # Original code Alexei Karpenko
  7. # Modified by Tim Zaman
  8. ##### Config #####
  9. sms_password = 'pwd'
  10. remote_phone_number = '+31612345678'
  11.  
  12. ##### Constants #####
  13. TRUE = 1
  14. FALSE = 0
  15. line_cut = FALSE
  16. had_fix = FALSE
  17. nofix_count = 0
  18.  
  19. ##### Modules #####
  20. #Use serial
  21. import SER
  22.  
  23. #Use build in module
  24. import MOD
  25.  
  26. #Use AT command interface
  27. import MDM
  28.  
  29. #Use GPIO functions
  30. import GPIO
  31.  
  32. ###### General Functions ######
  33. def cutoff():
  34.     GPIO.setIOvalue(8, 1)
  35.     #sms_send(remote_phone_number,'Cut line!')
  36.  
  37. def cutoff_cancel():
  38.     GPIO.setIOvalue(8, 0)
  39.  
  40. def getActualPosition():
  41.     MDM.send('AT$GPSACPr', 0)
  42.     pos = MDM.receive(1)
  43.     pos = pos.split('rn')
  44.     return pos[1]
  45.  
  46. def reset_system():
  47.     MDM.send('AT#SHDNr', 0)
  48.     temp = MDM.receive(1)
  49.  
  50. def reset_gps():
  51.     MDM.send('AT$GPSR=1r', 0)
  52.     temp = MDM.receive(1)
  53.  
  54. def reset_gps2():
  55.     MDM.send('AT$GPSR=0r', 0)
  56.     temp = MDM.receive(1)
  57.  
  58. #Debug message
  59. def debugmsg(msgtext):
  60.     msgtext = msgtext.replace('r', 'r')
  61.     msgtext = msgtext.replace('n', 'n')
  62.     print msgtext
  63.     SER.send(msgtext + 'rn')
  64.     #f = open('log.txt','ab')
  65.     #f.write(msgtext + 'n')
  66.     #f.close()
  67.  
  68. #GPS status
  69. def gps_status(gpspos):
  70.     global had_fix
  71.     debugmsg('Retrieving GPS status')
  72.     gpspos_parts = gpspos.split(',')
  73.  
  74.     if ( (gpspos_parts[5] == '2') or (gpspos_parts[5] == '3') ): #2D or 3D fix
  75.         debugmsg('GPS fix "' + gpspos_parts[5] + '" ie valid');
  76.         status = TRUE
  77.  
  78.         # GPS crash prevention:
  79.         if (gpspos_parts[10] == '00'):
  80.             if (had_fix == TRUE):
  81.                 reset_gps()
  82.                 had_fix = FALSE
  83.         else:
  84.             had_fix = TRUE
  85.     else:
  86.         debugmsg('GPS fix "' + gpspos_parts[5] + '" ie not valid');
  87.         status = FALSE
  88.  
  89.         # GPS crash prevention:
  90.         if (had_fix == TRUE):
  91.             reset_gps()
  92.             had_fix = FALSE
  93.  
  94.     return status
  95.  
  96. ###### SMS Process Functions ######
  97. #Process SMS messages
  98. def sms_proceses(gpspos):
  99.     debugmsg('Process SMS')
  100.     #List SMS
  101.     smsmsgs = sms_list()
  102.     totalsms = len(smsmsgs)
  103.     #debugmsg('SMS total: %d' % totalsms)
  104.  
  105.     #Go throguh SMS messages
  106.     for smsmsgindex in range(0, totalsms):
  107.         debugmsg('Message: %d' % smsmsgindex)
  108.         #Delete SMS (it is being processed now)
  109.         status = sms_delete(smsmsgs[smsmsgindex]['id'])
  110.  
  111.         #If status deleted ok
  112.         if (status == TRUE):
  113.             #debugmsg('List SMS id: %d, from: %s, msg: %s' % (smsmsgs[smsmsgindex]['id'], smsmsgs[smsmsgindex]['from'], smsmsgs[smsmsgindex]['msg']))
  114.  
  115.             #Split SMS message into parts
  116.             smsmsgparts = smsmsgs[smsmsgindex]['msg'].split(' ')
  117.  
  118.             #If at least 2 parts
  119.             if (len(smsmsgparts) > 1):
  120.  
  121.                 #Check if password valid
  122.                 if (smsmsgparts[0].lower() == sms_password):
  123.                     debugmsg('Password valid')
  124.                     #If position requested
  125.                     if (smsmsgparts[1].lower() == 'pos') or (smsmsgparts[1].lower() == 'position'):
  126.                         debugmsg('Action: Position requested')
  127.                         #If GPS position fix valid
  128.                         if (gps_status(gpspos) == TRUE):
  129.                             #gpsdataparts = gpspos.split(',')
  130.                             #senddata = 'Lat: ' + gpsdataparts[1] + ', Lon: ' + gpsdataparts[2] + ', Heading: ' + gpsdataparts[6] + ', Speed: ' + gpsdataparts[7] + ' km/hr'
  131.                             senddata = gpspos;
  132.                         else:
  133.                             senddata = 'No GPS Fix'
  134.  
  135.                         #Send SMS
  136.                         sms_send(smsmsgs[smsmsgindex]['from'], senddata)
  137.  
  138.                     elif (smsmsgparts[1].lower() == 'cut'):
  139.                         cutoff()
  140.  
  141.                     elif (smsmsgparts[1].lower() == 'oops'):
  142.                         cutoff_cancel()
  143.  
  144.                     elif (smsmsgparts[1].lower() == 'rgps'):
  145.                         reset_gps()
  146.  
  147.                     elif (smsmsgparts[1].lower() == 'rgps2'):
  148.                         reset_gps2()
  149.  
  150.                     elif (smsmsgparts[1].lower() == 'rsys'):
  151.                         reset_system()
  152.  
  153.                     else:
  154.                         debugmsg('Action: Unknown')
  155.  
  156.                 else:
  157.                     debugmsg('Password invalid')
  158.         else:
  159.             debugmsg('Unable to delete')
  160.  
  161. ###### SMS Library Functions ######
  162. #Setup SMS
  163. def sms_setup():
  164.     debugmsg('Setting up SMS')
  165.     MDM.send('AT+CMGF=1r', 0)
  166.     res = MDM.receive(50)#5 sec
  167.     MOD.sleep(1)#wait 0.1sec
  168.     debugmsg('SMS setup: ' + res)
  169.  
  170. #List SMS
  171. def sms_list():
  172.     #Note: Command will not return anything if SIM is not ready
  173.     debugmsg('List SMS')
  174.     #MDM.send('AT+CMGL="REC UNREAD"r', 0) #ALL REC UNREAD   STO SENT
  175.     MDM.send('AT+CMGL="REC UNREAD"r', 0)
  176.  
  177.     #smslist = ''
  178.     #res = MDM.receive(50)#5 sec
  179.     #smslist = smslist + res
  180.  
  181.     #while (res.find('rnOKrn') == -1):
  182.     #    res = MDM.receive(10)
  183.     #    smslist = smslist + res
  184.  
  185.     smslist = ''
  186.     res = ''
  187.  
  188.     timeout = MOD.secCounter() + 60
  189.     while ( (res.find('rnOKrn') == -1) and (MOD.secCounter() < timeout) ):
  190.         debugmsg('Timeout now: %d timeout: %d' % (MOD.secCounter(), timeout))
  191.         res = MDM.receive(50)
  192.         smslist = smslist + res
  193.         #debugmsg('get %s' % smslist)
  194.  
  195.     MOD.sleep(1)#wait 0.1sec
  196.     smsparts = smslist.split('rn')
  197.     smspartslen = len(smsparts)
  198.     #debugmsg('SMS Parts %d' % smspartslen)
  199.     smsmsgs = []
  200.     if (smspartslen-2 > 0) and (smsparts[smspartslen-2] == 'OK'):
  201.         #If there is at least 1 message
  202.         if (smspartslen >= 6):
  203.             #Go through all messages
  204.             for partno in range(1, smspartslen):
  205.                 #Find out if this is an cmgl line
  206.                 cmglparts = smsparts[partno].split('CMGL: ')
  207.                 #If at least 2 parts, leading cmgl and data
  208.                 if (len(cmglparts) > 1) and (cmglparts[0] == '+') and (partno+1 <= smspartslen):
  209.                     #debugmsg('Found CMGL %d: %s' % (partno, smsparts[partno]))
  210.                     #debugmsg('Text %d: %s' % (partno+1, smsparts[partno+1]))
  211.                     #Split msg info line into parts
  212.                     msginfoparts = cmglparts[1].split(',')
  213.                     #Check have all parts
  214.                     #if (len(msginfoparts) > 5):
  215.                     if (len(msginfoparts) > 4):
  216.  
  217.  
  218.                         #int() may fail
  219.  
  220.                         try:
  221.  
  222.  
  223.  
  224.                             #Convert id to integer
  225.  
  226.                             msginfoparts[0] = int(msginfoparts[0])
  227.  
  228.  
  229.  
  230.                             #Remove quotes
  231.  
  232.                             msginfoparts[2] = msginfoparts[2].replace('"', '')
  233.  
  234.                             #msginfoparts[4] = msginfoparts[4].replace('"', '')
  235.  
  236.                             #msginfoparts[5] = msginfoparts[5].replace('"', '')
  237.  
  238.  
  239.  
  240.                             smsmsgs.append({'id': msginfoparts[0], 'from': msginfoparts[2], 'msg': smsparts[partno+1]})
  241.  
  242.  
  243.  
  244.                             debugmsg('SMS id: %d, from: %s, msg: %s' % (msginfoparts[0], msginfoparts[2], smsparts[partno+1]))
  245.  
  246.  
  247.  
  248.                         except Exception:
  249.  
  250.                             debugmsg('Error CMGL %d: %s' % (partno, smsparts[partno]))
  251.  
  252.  
  253.  
  254.         debugmsg('SMS total: %d' % len(smsmsgs))
  255.  
  256.  
  257.  
  258.     else:
  259.  
  260.  
  261.  
  262.         debugmsg('No SMS messages available')
  263.  
  264.  
  265.  
  266.     return smsmsgs
  267.  
  268.  
  269.  
  270. #SMS Delete
  271.  
  272. def sms_delete(delindex):
  273.  
  274.  
  275.  
  276.     debugmsg('SMS Delete: %d' % delindex)
  277.  
  278.  
  279.  
  280.     MDM.send('AT+CMGD=' + str(delindex) + 'r', 0)
  281.  
  282.     res = MDM.receive(50)#5 sec
  283.  
  284.     MOD.sleep(1)#wait 0.1sec
  285.  
  286.  
  287.  
  288.     if (res == 'rnOKrn'):
  289.  
  290.         status = TRUE
  291.  
  292.     else:
  293.  
  294.         status = FALSE
  295.  
  296.  
  297.  
  298.     return status
  299.  
  300.  
  301.  
  302. #SMS Send
  303.  
  304. def sms_send(to, text):
  305.  
  306.  
  307.  
  308.     debugmsg('Send SMS to: %s, MSG: %s' % (to, text))
  309.  
  310.  
  311.  
  312.     MDM.send('AT+CMGS="' + to + '"r', 0)
  313.  
  314.     res = MDM.receive(50)#5 sec
  315.  
  316.     MOD.sleep(1)#wait 0.1sec
  317.  
  318.  
  319.  
  320.     #Check for SMS prompt
  321.  
  322.     if (res == 'rn> '):
  323.  
  324.  
  325.  
  326.         #Send SMS message text
  327.  
  328.         MDM.send(text, 0)
  329.  
  330.  
  331.  
  332.         #End SMS
  333.  
  334.         MDM.sendbyte(0x1A, 0)
  335.  
  336.         res2 = MDM.receive(180)#5 sec
  337.  
  338.         MOD.sleep(1)#wait 0.1sec
  339.  
  340.  
  341.  
  342.         if (res2.find('rnOKrn') != -1):
  343.  
  344.  
  345.  
  346.             debugmsg('SMS sent')
  347.  
  348.  
  349.  
  350.             status = TRUE
  351.  
  352.  
  353.  
  354.         else:
  355.  
  356.  
  357.  
  358.             debugmsg('SMS Send: ' + res2)
  359.  
  360.  
  361.  
  362.             debugmsg('Did not get SMS sent confirmation')
  363.  
  364.  
  365.  
  366.             status = FALSE
  367.  
  368.             MDM.receive(1); # may prevent +CMS ERROR: 331 crash
  369.  
  370.  
  371.  
  372.     else:
  373.  
  374.  
  375.  
  376.         debugmsg('SMS Send: ' + res)
  377.  
  378.  
  379.  
  380.         debugmsg('Did not receive SMS prompt')
  381.  
  382.  
  383.  
  384.         #Abort SMS (just in case)
  385.  
  386.         MDM.sendbyte(0x1B, 0)
  387.  
  388.         MOD.sleep(1)#wait 0.1sec
  389.  
  390.  
  391.  
  392.         status = FALSE
  393.  
  394.  
  395.  
  396.     return status
  397.  
  398.  
  399.  
  400.  
  401.  
  402.  
  403.  
  404. ##################################################################################
  405.  
  406.  
  407.  
  408. def constraints(gpspos):
  409.  
  410.     global line_cut
  411.  
  412.     gpspos_parts = gpspos.split(',')
  413.  
  414.     if ( (gpspos_parts[5] == '2') or (gpspos_parts[5] == '3') ): #2D or 3D fix
  415.  
  416.         lat_temp = gpspos_parts[1].split('.')
  417.  
  418.         lon_temp = gpspos_parts[2].split('.')
  419.  
  420.         lat = int(lat_temp[0])
  421.  
  422.         lon = int(lon_temp[0])
  423.  
  424.         debugmsg('lat = ' + repr(lat) + ' lon = ' + repr(lon))
  425.  
  426.         if ( (4320 < lat) and (8036 < lon) ):
  427.  
  428.             debugmsg('Within constraints.')
  429.  
  430.         elif ( line_cut == FALSE ):
  431.  
  432.             debugmsg('Outside of constraints, cutting line!')
  433.  
  434.             cutoff()
  435.  
  436.             line_cut = TRUE
  437.  
  438.         else:
  439.  
  440.             debugmsg('Outside of constraints.')
  441.  
  442.  
  443.  
  444. ###### Init ######
  445.  
  446.  
  447.  
  448. # configure GPIO pins
  449.  
  450. GPIO.setIOdir(8,0,1) # GPIO8 0 output
  451.  
  452.  
  453.  
  454. SER.set_speed('115200','8N1')
  455.  
  456. SER.send('rn--------------------rnrn')
  457.  
  458.  
  459.  
  460. debugmsg('Running...');
  461.  
  462.  
  463.  
  464. #Set verbose error reporting
  465.  
  466. MDM.send('AT+CMEE=2r', 0)
  467.  
  468. MDM.receive(50)#5 sec
  469.  
  470. MOD.sleep(1)#wait 0.1sec
  471.  
  472.  
  473.  
  474. #May be required in North America / Canada to switch the unit to 900/1900MHz frequency (uncomment if in those areas)
  475.  
  476. #MDM.send('AT#BND=3r', 0)
  477.  
  478. #MDM.receive(1)#0.1sec
  479.  
  480. #MOD.sleep(1)#wait 0.1sec
  481.  
  482.  
  483.  
  484. gpspos_lastvalid = ''
  485.  
  486.  
  487.  
  488. #Setup SMS
  489.  
  490. sms_setup()
  491.  
  492.  
  493.  
  494. #Main loop
  495.  
  496. while 1:
  497.  
  498.  
  499.  
  500.     debugmsg('Entering loop')
  501.  
  502.  
  503.  
  504.     #Retrieve current position
  505.  
  506.     gpspos = getActualPosition()
  507.  
  508.  
  509.  
  510.     debugmsg('Position: %s' % gpspos)
  511.  
  512.  
  513.  
  514.     constraints(gpspos)
  515.  
  516.  
  517.  
  518.     # A few tests:
  519.  
  520.     #constraints('002317.908,4326.9364N,08027.2974W,4.0,286.4,2,152.30,10.94,5.90,110907,03')
  521.  
  522.     #constraints('002317.908,4326.9364N,08211.2974W,4.0,286.4,2,152.30,10.94,5.90,110907,03')
  523.  
  524.  
  525.  
  526.     #Retrieve GPS fix status
  527.  
  528.     gps_statusnow = gps_status(gpspos)
  529.  
  530.  
  531.  
  532.     # GPS crash prevention (reset GPS after ~10 minutes if no fix)
  533.  
  534.     if (had_fix == FALSE):
  535.  
  536.         nofix_count = nofix_count + 1
  537.  
  538.         if (nofix_count == 60):
  539.  
  540.             reset_gps()
  541.  
  542.             nofix_count = 0
  543.  
  544.     else:
  545.  
  546.         nofix_count = 0
  547.  
  548.  
  549.  
  550.     #Save last valid position
  551.  
  552.     #If position fix valid, or none recorded already, use last retrieved
  553.  
  554.     if ( (gps_statusnow == TRUE) or (gpspos_lastvalid == '') ):
  555.  
  556.         gpspos_lastvalid = gpspos
  557.  
  558.  
  559.  
  560.     #Check for / process new SMS messages
  561.  
  562.     sms_proceses(gpspos_lastvalid)
  563.  
  564.  
  565.  
  566.     debugmsg('Powersave for 10 seconds')
  567.  
  568.  
  569.  
  570.     #Powersave for 10 seconds
  571.  
  572.     MOD.powerSaving(10)

Tim Zaman

MSc Biorobotics. Specialization in computer vision and deep learning. Works at NVIDIA.

You may also like...

7 Responses

  1. Pavol says:

    Hello Tim,
    how can i order your board?

    thank you in advance

  2. Jay says:

    How much for the module?

  3. Reuben Coelho says:

    Hey Tim,

    I am wondering if you could provide the eagle files for this project.
    Or is it possible for me to order one of these boards of you.

    Regards
    Reuben Coelho

  4. Can you provide the Eagle files for the PCB? Thanks!

  5. Hoi Tim,

    Ik heb hier een aantal GM862 modules liggen en een beagleboard is onderweg.

    Om het wiel niet opnieuw uit te vinden zou ik graag gebruik maken van de pcb die jij hebt ontwikkeld. Heb je hier nog pcb’s van? Of het ontwerp? Dat zou mij snel op weg helpen. Uiteraard deel ik mijn bevindingen met je en zal ik onthouden dat jij de ontwikkelaar van het board bent.

    Kunnen we hier een keer contact over hebben?

    Met vriendelijke groet,
    Tijs van Roon

    • Tim says:

      Hoi Thijs, we kunnen overal contact over hebben, maar het schematic bijvoorbeeld staat hier http://www.timzaman.nl/?p=1165&lang=en . Als je een PCBtje wilt hebben kun je een euro of 5 paypallen, heb er nog een aantal liggen.

      • Hoi Tim,

        bedankt voor je antwoord. Ik heb helemaal niet opgemerkt dat hier een antwoord stond. Had een mailtje van je blog verwacht. Stom! Excuus dus!

        Het schema komt me goed van pas. Ik ga het (in de loop van de tijd) met de BeagleBone proberen die ik hier heb liggen. Die is voor mij wat praktischer.

        Ik laat je mijn bevindingen weten. Bedankt voor je hulp!