Getting started with the pilot nexus on Raspberry Pi – Making a GSM Phone

My wife asked me if i could make something so the kids could give us a call on the phone without actually giving them a phone.
They are too young for a full phone but some device that calls mom or dad would be handy, so i decided to build  a small phone with the Pilot Nexus on Raspberry Pi.

I use the 2G GSM Module,  the 8 Input Module, the 8 Outputs module, a small microphone, an 8 ohm speaker and a large button with background illumination.

Setting up the Raspberry Pi

I started with the Pilotetcher  and installed the latest image for the RaspberryPi.
The pilotetcher tool is pretty cool. It automatically fetches the latest precompiled raspbian based image from the pilot server and has all the pilot tools already installed.
Once flashed i installed the SDcard into a Raspberry pi and logged in over ssh.

overflo@fnord:~$ ssh pi@192.168.101.77
The authenticity of host ‘192.168.101.77 (192.168.101.77)’ can’t be established.
ECDSA key fingerprint is SHA256:7B6/EwyYzzNIm2Lh3WLtATe9r5MtZ3QzNiXBUGheA7U.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added ‘192.168.101.77’ (ECDSA) to the list of known hosts.
pi@192.168.101.77’s password: raspberry
Linux raspberrypi 4.14.79-v7+ #1159 SMP Sun Nov 4 17:50:20 GMT 2018 armv7l
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Wed Jan 16 13:34:50 2019
SSH is enabled and the default password for the ‘pi’ user has not been changed.
This is a security risk – please login as the ‘pi’ user and type ‘passwd’ to set a new password.
pi@raspberrypi:~ $

Once logged in i run ‘sudo pilot setup’

pi@raspberrypi:~ $ sudo pilot setup
Pilot Configuration Tool v0.4.4
Module 1: GSM 2G
Module 2: 8 Digital Inputs *
Module 3: 8 Digital Outputs
Module 4: –
Modules marked with an Asterisk (*) have multiple firmware configurations
Press Module Number [2] to change selected firmware.
Do you want to build and program the Pilot Nexus Firmware? (y/n/2): y
checking if firmware is available…available
downloading firmware…done
erasing CPLD…done
programming MCU…done
programming CPLD…done
reloading drivers…done
Do you want to register the Node? (required for remote access) (y/n) n
To get help on how to use the modules, run ‘pilot module
pi@raspberrypi:~ $


Lets check out this pilot module command.

pi@raspberrypi:/proc/pilot/module1 $ pilot module
Pilot Configuration Tool v0.4.4

Module [1] GSM 2G:
use /dev/ttyP0 to access the GSM module. You need to enable the module first
Example 1: enable GSM module
echo 1 > /proc/pilot/module1/enable_gsm

Module [2] 8 Digital Inputs:
Access the outputs through the standard Linux GPIO interface. The calculated base for this module is 55
To make sure that the base calculation is correct, you can check the existence of the directory /sys/class/gpio/gpiochip55, it should contain a file named ‘label’ with the content ‘piloti8_1’
Example 1: configure and read input 0
echo 55 > /sys/class/gpio/export
cat /sys/class/gpio/gpio55/value


Module [3] 8 Digital Outputs:
Access the outputs through the standard Linux GPIO interface. The calculated base for this module is 64
To make sure that the base calculation is correct, you can check the existence of the directory /sys/class/gpio/gpiochip64, it should contain a file named ‘label’ with the content ‘piloto8_2’
Example 1: Configure and write 1 (high) to output 0
echo 64 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio64/direction
echo 1 > /sys/class/gpio/gpio64/value

Aha! 
We want to expose the GPIO pins to the /sys filesystem and we want to enable the GSM.
Lets make a small shellscript to enable those things during bootup and save it to /home/pi/startphone.sh

!/bin/bash
echo “Enabling GSM”
echo 1 > /proc/pilot/module1/enable_gsm


echo “Exporting GPIO55 (Button)”
echo 55 > /sys/class/gpio/export

echo “Exporting LED”
echo 64 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio64/direction

Now we add this script to the local startup in /etc/rc.local to run it everytime the Raspberry Pi is turned on.

Its a good time to reboot the rpi now.

It should turn on the led for one second and off again now.

Wiring the modules

For this project i am using the GSM module, an 8 Input and an 8 Output module.
The GSM module is located at slot 1 of the pilot nexus motherboard.
The 8 Input module is located at slot 2
The 8 Output module sits in slot 3
Slot4 is empty

Modules inserted
Dont forget to connect an antenna ..or you wont be able to receive a GSM signal
Connect the Microphone and speaker to the pins on the  GSM module
The Button connects to the + and I0 pin on the 8 Inputmodule,  The Led connects to GND and O0 on the Outputmodule

Some python code

# this code uses python3 syntax. will not work with python 2.x

from serial import Serial
import atexit, logging, sys, time
from time import sleep
from enum import IntEnum
from datetime import datetime


PORT="/dev/ttyP0"
BAUD=9600
DATE_FMT='"%y/%m/%d,%H:%M:%S%z"'

PHONENUMBER="+4365000000000" # number to dial
SIM_PIN="9673" # None  # None if there is no need for s PIN




class ATResp(IntEnum):
    ErrorNoResponse=-1
    ErrorDifferentResponse=0
    OK=1


class modem(object):
    def __init__(self, port, baud, logger=None, loglevel=logging.WARNING):
        self._port=port
        self._baud=baud

        self._ready=False
        self._serial=None

        self._active_call=False
class modem(object):
    def __init__(self, port, baud, logger=None, loglevel=logging.WARNING):
        self._port=port
        self._baud=baud
        if logger: self._logger=logger
        else:
            self._logger=logging.getLogger("SMS")
            handler=logging.StreamHandler(sys.stdout)
            handler.setFormatter(logging.Formatter("%(asctime)s : %(levelname)s -> %(message)s"))
            self._logger.addHandler(handler)
            self._logger.setLevel(loglevel)

    def setup(self):
        """
        Setup the IO to control the power and reset inputs and the serial port.
        """
        self._logger.debug("Setup")
    
        self._serial=Serial(self._port, self._baud)

    def reset(self):
        """
        Reset (turn on) the SIM800 module by taking the power line for >1s
        and then wait 5s for the module to boot.
        """
        self._logger.debug("Reset do nothing")


    def sendATCmdWaitResp(self, cmd, response, timeout=.5, interByteTimeout=.1, attempts=1, addCR=False):
        """
        This function is designed to check for simple one line responses, e.g. 'OK'.
        """
        self._logger.debug("Send AT Command: {}".format(cmd))
        self._serial.timeout=timeout
        self._serial.inter_byte_timeout=interByteTimeout

        status=ATResp.ErrorNoResponse
        for i in range(attempts):
            bcmd=cmd.encode('utf-8')+b'\r'
            if addCR: bcmd+=b'\n'

            self._logger.debug("Attempt {}, ({})".format(i+1, bcmd))
            #self._serial.write(cmd.encode('utf-8')+b'\r')
            self._serial.write(bcmd)
            self._serial.flush()

            lines=self._serial.readlines()
            lines=[l.decode('utf-8').strip() for l in lines]
            lines=[l for l in lines if len(l) and not l.isspace()]
            self._logger.debug("Lines: {}".format(lines))
            if len(lines)<1: continue
            line=lines[-1]
            self._logger.debug("Line: {}".format(line))

            if not len(line) or line.isspace(): continue
            elif line==response: return ATResp.OK            
            else: return ATResp.ErrorDifferentResponse
        return status

    def sendATCmdWaitReturnResp(self, cmd, response, timeout=.5, interByteTimeout=.1):
        """
        This function is designed to return data and check for a final response, e.g. 'OK'
        """        
        self._logger.debug("Send AT Command: {}".format(cmd))
        self._serial.timeout=timeout
        self._serial.inter_byte_timeout=interByteTimeout

        self._serial.write(cmd.encode('utf-8')+b'\r')
        self._serial.flush()
        lines=self._serial.readlines()
        for n in range(len(lines)):
            try: lines[n]=lines[n].decode('utf-8').strip()
            except UnicodeDecodeError: lines[n]=lines[n].decode('latin1').strip()

        lines=[l for l in lines if len(l) and not l.isspace()]        
        self._logger.debug("Lines: {}".format(lines))

        if not len(lines): return (ATResp.ErrorNoResponse, None)

        _response=lines.pop(-1)
        self._logger.debug("Response: {}".format(_response))
        if not len(_response) or _response.isspace(): return (ATResp.ErrorNoResponse, None)
        elif response==_response: return (ATResp.OK, lines)
        return (ATResp.ErrorDifferentResponse, None)

    def parseReply(self, data, beginning, divider=',', index=0):
        """
        Parse an AT response line by checking the reply starts with the expected prefix,
        splitting the reply into its parts by the specified divider and then return the 
        element of the response specified by index.
        """
        self._logger.debug("Parse Reply: {}, {}, {}, {}".format(data, beginning, divider, index))
        if not data.startswith(beginning): return False, None
        data=data.replace(beginning,"")
        data=data.split(divider)
        try: return True,data[index]
        except IndexError: return False, None

    def getSingleResponse(self, cmd, response, beginning, divider=",", index=0, timeout=.5, interByteTimeout=.1):
        """
        Run a command, get a single line response and the parse using the
        specified parameters.
        """
        status,data=self.sendATCmdWaitReturnResp(cmd,response,timeout=timeout,interByteTimeout=interByteTimeout)
        if status!=ATResp.OK: return None
        if len(data)!=1: return None
        ok,data=self.parseReply(data[0], beginning, divider, index)
        if not ok: return None
        return data


    def enterPin(self):
        if SIM_PIN:
            status=self.sendATCmdWaitResp("AT+CPIN=" + SIM_PIN, "OK", attempts=5)
            if status==ATResp.OK:
                self._logger.debug("Correct PIN")



    


    def turnOn(self):
        """
        Check to see if the module is on, if so return. If not, attempt to
        reset the module and then check that it is responding.
        """
        self._logger.debug("Turn On")
        for i in range(2):
            status=self.sendATCmdWaitResp("AT", "OK", attempts=5)
            if status==ATResp.OK:
                self._logger.debug("GSM module ready.")
                self._ready=True
                return True
            elif status==ATResp.ErrorDifferentResponse:
                self._logger.debug("GSM module returned invalid response, check baud rate?")
            elif i==0:
                self._logger.debug("GSM module is not responding, resetting...")
                self.reset()
            else: self._logger.error("GSM module failed to respond after reset!")
        return False


    def setEchoOff(self):
        """
        Switch off command echoing to simply response parsing.
        """
        self._logger.debug("Set Echo Off")
        self.sendATCmdWaitResp("ATE0", "OK")
        status=self.sendATCmdWaitResp("ATE0", "OK")
        return status==ATResp.OK




class piphone(modem):
    def __init__(self, port, baud, logger=None, loglevel=logging.WARNING):
        self._port=port
        self._baud=baud

        self._ready=False
        self._serial=None

        self._active_call=False

        if logger: self._logger=logger
        else:
            self._logger=logging.getLogger("SMS")
            handler=logging.StreamHandler(sys.stdout)
            handler.setFormatter(logging.Formatter("%(asctime)s : %(levelname)s -> %(message)s"))
            self._logger.addHandler(handler)
            self._logger.setLevel(loglevel)



    def call(self, phonenumber):
        if not self._active_call:
            status=self.sendATCmdWaitResp('ATD{};'.format(phonenumber), "OK")
            self._active_call=True            
            return True

        print("There is already a call happening.. Hanging up")
        self.hangUp()
        self._active_call=False
        return False
     
    def hangUp(self):
        if self._active_call:
            self.sendATCmdWaitResp('ATH', "OK")


class uihandler(object):
    def __init__(self,phone):
        self.button1_pressed=False
        self.led1_on=False

        self.phone=phone
        self.led1 = open("/sys/class/gpio/gpio64/value","w")
        self.button1 =open("/sys/class/gpio/gpio55/value","r")


    def readButtons(self):
        self.button1.seek(0)
        val= self.button1.readline()
        if ("1" in val):
            if not self.button1_pressed:
                print("Button 1 pressed")
                if(self.phone.call(PHONENUMBER)): 
                    self.setLed(True)
                else:
                    self.setLed(False)
                    self.button1_pressed=True
                    time.sleep(0.1)
        else:
            self.button1_pressed=False


    def setLed(self, state):
        if state: 
            onoff ="0"
        else:
            onoff="1"
        self.led1.seek(0)
        self.led1.write(onoff)



if __name__=="__main__":
    s=piphone(PORT,BAUD,loglevel=logging.DEBUG)

    u =uihandler(s)
    u.setLed(False)


    s.setup()
    if not s.turnOn(): exit(1)

    s.enterPin() # may fail if already initalized or incorrect pin ..
    if not s.setEchoOff(): exit(1)
    print("Good to go!")

    while True:
        u.readButtons()

Share This Post: