From f2a3b238723d2ad385456a4d24ec6ce87e3cf3b9 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Sun, 19 Aug 2018 18:14:37 +0100 Subject: [PATCH 01/24] Initial upload of SIM800 library Base command processing is done. A selecting of commands implemented (mostly call and network). --- lib/sim800 | 223 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 lib/sim800 diff --git a/lib/sim800 b/lib/sim800 new file mode 100644 index 0000000..6fec9c1 --- /dev/null +++ b/lib/sim800 @@ -0,0 +1,223 @@ +import machine +import time + +uart_port = 1 +uart_default_baud = 115200 +uart_timeout = 28 +default_responce_timeout = 8000 + +status_pin = Pin(6, Pin.IN) +ringer_pin = Pin(8, Pin.IN) +pwr_key_pin = Pin(23, Pin.OUT) + +# Open the UART +uart = machine.UART(uart_port, uart_default_baud, mode=UART.BINARY, timeout=uart_timeout) +dirtybuffer = False # Flag if the buffer could have residual end of responces line in it? + +# Check if the SIM800 is powered up +def ison(): + return status_pin.value()==1 + +# Check if the SIM800 is ringing +def isringing(): + return ringer_pin.value()==0 + +# Identify if this was a positive responce +def ispositive(responce): + return (responce=="OK") or responce.startswith("CONNECT") + +# Identify if this was a negative responce +def isnegative(responce): + return (responce=="NO CARRIER") or (responce=="ERROR") or (responce=="NO DIALTONE") or (responce=="BUSY") or (responce=="NO ANSWER") + +# Extract the [first] parameter from a responce +def extractval(parameter, responce, default=""): + for entry in responce: + if entry.startswith(parameter): + return (entry[len(parameter):]).strip() + return default + +# Read a lines of responce from the UART +def readline(): + stringin = "" + while (True): + charin = uart.read(1) + # If we time out we are at the end of a line + if charin is None: + return stringin + # Look for all EOL chars just in case settigns were changed + elif (charin == b'\n') or (charin == b'\r'): + if (stringin!=""): + return stringin + # This will be part of the string then + else: + stringin += str(charin, "ASCII") + +# Execute a command on the module +# command is the AT command without the AT or CR/LF, responce_timeout (in ms) is how long to wait for completion, custom_endofdata is to wait for a non standard bit of +def command(command="", responce_timeout=default_responce_timeout, custom_endofdata=None): + # Empty the buffer + uart.read() + # Send the command + uart.write("AT" + command + "\r\n") + # Read the results + result = [] + complete = False + customcomplete = custom_endofdata is None + timeouttime = time.time()+(responce_timeout/1000) + while (time.time()0): + result.append(line) + # Check if we have a standard end of responce + if (ispositive(line)) or (isnegative(line)): + complete = True + # Check if we have the data we are looking for + if (custom_endofdata is not None) and (line.startswith(custom_endofdata)): + customcomplete = True + # Check if we are done + if complete and customcomplete: + return result + # We ran out of time + # set the dirty buffer flag is an out of date end of responcs cound end up in the buffer + if custom_endofdata is None: + global dirtybuffer + dirtybuffer = True + result.append("TIMEOUT") + return result + +# Power on the SIN800 +def poweron(): + # Only power on if we are off + if not ison(): + # Check that the power pin has not beel left high + if (pwr_key_pin.value()): + pwr_key_pin.off() + time.sleep(1) + pwr_key_pin.on() + time.sleep(3) + pwr_key_pin.off() + # Send a command to autonigotiate UART speed + command() + time.sleep(2) + uart.read() # Empty the buffer + +# Power off the SIN800 +def poweroff(): + # Only power off if we are on + if (status_pin.value()): + # Check that the power pin has not beel left high + if (pwr_key_pin.value()): + pwr_key_pin.off() + time.sleep(1) + pwr_key_pin.on() + time.sleep(3) + pwr_key_pin.off() + uart.read() # Empty the buffer + dirtybuffer = False + +# Change the speed on the communication +def uartspeed(newbaud): + global uart + command("+IPR=" + str(newbaud)) + uart.deinit() + if (newbaud==0): + uart = machine.UART(uart_port, uart_default_baud, mode=UART.BINARY, timeout=uart_timeout) + else: + uart = machine.UART(uart_port, newbaud, mode=UART.BINARY, timeout=uart_timeout) + +# Make a voice call +def call(number): + command("D" + str(newbaud) + ";") + +# Answer a voice call +def answer(number): + command("A") + +# End a voice call +def handup(): + command("H") + +# Get/Set ringer volume (0-100) +def ringervolume(level=None): + # Set the new leve if we have one to set + if level is not None: + command("+CRSL=" + str(level)) + # Retieve the set level to report back + responce = command("+CRSL?") + return int(extractval("+CRSL:", responce, 0)) + +# Get/Set speaker volume (0-100) +def speakervolume(level=None): + # Set the new leve if we have one to set + if level is not None: + command("+CLVL=" + str(level)) + # Retieve the set level to report back + responce = command("+CLVL?") + return int(extractval("+CLVL:", responce, 0)) + +# Is the battery charging (0=no, 1=yes, 2=full) +def batterycharging(): + responce = command("+CBC") + vals = extractval("+CBC:", responce, "0,0,0").split(",") + return int(vals[0]) + +# How full is the battery (1-100) +def batterycharge(): + responce = command("+CBC") + vals = extractval("+CBC:", responce, "0,0,0").split(",") + return int(vals[1]) + +# List the available operator (returns list of [0=?,1=available,2=current,3=forbidden], 0=long name, 1=short name, 2=GSMLAI ) +def listoperators(available_only=True): + delim = "||||" + responce = command("+COPS=?", 45000) + responcedata = extractval("+COPS:", responce, "").split(",,")[0] + responcelist = responcedata.replace("),(",delim)[1:-1].split(delim) + results = [] + for entry in responcelist: + subresults = [] + for subentry in entry.split(","): + subresults.append(subentry.strip("\"")) + if (not available_only) or (subresults[0]=="1") or (subresults[0]=="2"): + results.append(subresults) + return results + +# Get the current operator (format 0=long name, 1=short name, 2=GSMLAI) +def currentoperator(format=0): + command("+COPS=3," + str(format)) + responce = command("+COPS?") + responcedata = extractval("+COPS:", responce, "").split(",") + if (len(responcedata)>=3): + return responcedata[2].strip("\"") + else: + return "" + +# Set the operator selection ([0=automatic,2=deregister]) +def soperator(mode, format=None, operator=None): + params = "" + if format is not None: + params += "," + str(format) + if operator is not None: + params += "," + str(operator) + command("+COPS=" + str(mode) + params, 120000) + +# Get the activity status (returns 0=ready, 2=unknown, 3=ringing, 4=call in progress) +def getstatus(): + responce = command("+CPAS") + vals = extractval("+CPAS:", responce, "2") + return int(extractval("+CPAS:", responce, "2")) + +# Get my number +def getmynumber(): + responce = command("+CUSD=1,\"*#100#\"", 8000, "+CUSD:") + responcedata = extractval("+CUSD:", responce, "").split(",") + if (len(responcedata)>=2): + return responcedata[1].strip().strip("\"") + else: + return "" + + +# Turn on the SIM800 +poweron() From f900bb320f54023e3e5bf3f472faf4c70b18d386 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Wed, 22 Aug 2018 13:48:42 +0100 Subject: [PATCH 02/24] Further command updates More commands added. A few efficiency updates. command() not accepts non AT commands. Timeouts localised. Non blocking power on/off option now available. --- lib/sim800 | 155 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 100 insertions(+), 55 deletions(-) diff --git a/lib/sim800 b/lib/sim800 index 6fec9c1..70c3375 100644 --- a/lib/sim800 +++ b/lib/sim800 @@ -4,7 +4,7 @@ import time uart_port = 1 uart_default_baud = 115200 uart_timeout = 28 -default_responce_timeout = 8000 +default_responce_timeout = 2000 status_pin = Pin(6, Pin.IN) ringer_pin = Pin(8, Pin.IN) @@ -24,7 +24,7 @@ def isringing(): # Identify if this was a positive responce def ispositive(responce): - return (responce=="OK") or responce.startswith("CONNECT") + return (responce=="OK") or responce.startswith("CONNECT") or responce.startswith("> ") # Identify if this was a negative responce def isnegative(responce): @@ -45,21 +45,26 @@ def readline(): # If we time out we are at the end of a line if charin is None: return stringin - # Look for all EOL chars just in case settigns were changed - elif (charin == b'\n') or (charin == b'\r'): + # We this the end of the line? + elif (charin == b'\r'): if (stringin!=""): return stringin # This will be part of the string then - else: + elif not (charin == b'\n'): stringin += str(charin, "ASCII") # Execute a command on the module # command is the AT command without the AT or CR/LF, responce_timeout (in ms) is how long to wait for completion, custom_endofdata is to wait for a non standard bit of -def command(command="", responce_timeout=default_responce_timeout, custom_endofdata=None): +def command(command="AT", responce_timeout=default_responce_timeout, custom_endofdata=None): + global dirtybuffer # Empty the buffer uart.read() + # Do not bother if we are powered off + if not ison(): + dirtybuffer = False + return ["POWERED OFF"] # Send the command - uart.write("AT" + command + "\r\n") + uart.write(command + "\r") # Read the results result = [] complete = False @@ -82,45 +87,46 @@ def command(command="", responce_timeout=default_responce_timeout, custom_endofd # We ran out of time # set the dirty buffer flag is an out of date end of responcs cound end up in the buffer if custom_endofdata is None: - global dirtybuffer dirtybuffer = True result.append("TIMEOUT") return result -# Power on the SIN800 -def poweron(): - # Only power on if we are off - if not ison(): - # Check that the power pin has not beel left high - if (pwr_key_pin.value()): - pwr_key_pin.off() - time.sleep(1) - pwr_key_pin.on() - time.sleep(3) +# Power on the SIM800 (True=on, False=off, returns true when on) +def power(onoroff, async): + # Get to a stable state if not async + if not async and pwr_key_pin.value(): pwr_key_pin.off() - # Send a command to autonigotiate UART speed - command() - time.sleep(2) - uart.read() # Empty the buffer - -# Power off the SIN800 -def poweroff(): - # Only power off if we are on - if (status_pin.value()): - # Check that the power pin has not beel left high - if (pwr_key_pin.value()): - pwr_key_pin.off() - time.sleep(1) - pwr_key_pin.on() time.sleep(3) + # Press the virtual power key if we are off + if not (ison()==onoroff): + pwr_key_pin.on() + if not async: + time.sleep(3) + # Have we just turned on? + isonnow = ison() + if (isonnow==onoroff) and pwr_key_pin.value(): + # Stop pressing the virtual key pwr_key_pin.off() - uart.read() # Empty the buffer + # Clear the buffer + uart.read() dirtybuffer = False + # Send a command to autonigotiate UART speed + if isonnow: + command("AT") + return isonnow + +# Power on the SIM800 (returns true when on) +def poweron(async=False): + power(True, async) + +# Power off the SIM800 +def poweroff(async=False): + power(False, async) # Change the speed on the communication def uartspeed(newbaud): global uart - command("+IPR=" + str(newbaud)) + command("AT+IPR=" + str(newbaud)) uart.deinit() if (newbaud==0): uart = machine.UART(uart_port, uart_default_baud, mode=UART.BINARY, timeout=uart_timeout) @@ -129,50 +135,81 @@ def uartspeed(newbaud): # Make a voice call def call(number): - command("D" + str(newbaud) + ";") + command("ATD" + str(number) + ";", 20000) # Answer a voice call -def answer(number): - command("A") +def answer(): + command("ATA", 20000) # End a voice call -def handup(): - command("H") +def hangup(): + command("ATH") + +# Redial the last number +def redial(): + command("ATDL") + +# Play DTMF tone(s) on a call +def dtmf(number): + validdigits = '1234567890#*ABCD' + for digit in str(number).upper(): + if (digit in validdigits): + command("AT+VTS=" + digit) + elif (digit==','): + time.sleep(1) + +# Send an SMS message +def sendsms(number, message): + # Swith to text mode + command("AT+CMGF=1") + # Switch to ASCII(ish) + command("AT+CSCS=\"8859-1\"") + # Send the message + command("AT+CMGS=\"" + str(number) + "\"") + return command(message + "\x1a", 60000) + +# Get the IMEI number of the SIM800 +def imei(): + responce = command("AT+GSN") + if (len(responce)>=2): + return responce[len(responce)-2] + else: + return "" # Get/Set ringer volume (0-100) def ringervolume(level=None): # Set the new leve if we have one to set if level is not None: - command("+CRSL=" + str(level)) + command("AT+CRSL=" + str(level)) # Retieve the set level to report back - responce = command("+CRSL?") + responce = command("AT+CRSL?") return int(extractval("+CRSL:", responce, 0)) # Get/Set speaker volume (0-100) def speakervolume(level=None): # Set the new leve if we have one to set if level is not None: - command("+CLVL=" + str(level)) + command("AT+CLVL=" + str(level)) # Retieve the set level to report back - responce = command("+CLVL?") + responce = command("AT+CLVL?") return int(extractval("+CLVL:", responce, 0)) # Is the battery charging (0=no, 1=yes, 2=full) def batterycharging(): - responce = command("+CBC") + responce = command("AT+CBC") vals = extractval("+CBC:", responce, "0,0,0").split(",") return int(vals[0]) # How full is the battery (1-100) def batterycharge(): - responce = command("+CBC") + responce = command("AT+CBC") vals = extractval("+CBC:", responce, "0,0,0").split(",") return int(vals[1]) # List the available operator (returns list of [0=?,1=available,2=current,3=forbidden], 0=long name, 1=short name, 2=GSMLAI ) def listoperators(available_only=True): delim = "||||" - responce = command("+COPS=?", 45000) + responce = command("AT+COPS=?", 45000) responcedata = extractval("+COPS:", responce, "").split(",,")[0] responcelist = responcedata.replace("),(",delim)[1:-1].split(delim) results = [] @@ -186,8 +223,8 @@ def listoperators(available_only=True): # Get the current operator (format 0=long name, 1=short name, 2=GSMLAI) def currentoperator(format=0): - command("+COPS=3," + str(format)) - responce = command("+COPS?") + command("AT+COPS=3," + str(format)) + responce = command("AT+COPS?") responcedata = extractval("+COPS:", responce, "").split(",") if (len(responcedata)>=3): return responcedata[2].strip("\"") @@ -201,23 +238,31 @@ def soperator(mode, format=None, operator=None): params += "," + str(format) if operator is not None: params += "," + str(operator) - command("+COPS=" + str(mode) + params, 120000) + command("AT+COPS=" + str(mode) + params, 120000) # Get the activity status (returns 0=ready, 2=unknown, 3=ringing, 4=call in progress) def getstatus(): - responce = command("+CPAS") + responce = command("AT+CPAS") vals = extractval("+CPAS:", responce, "2") return int(extractval("+CPAS:", responce, "2")) + +# Request Unstructured Supplementary Service Data from network +def ussd(ussdstring, timeout=8000): + responce = command("AT+CUSD=1,\"" + ussdstring + "\"", timeout, "+CUSD:") + return extractval("+CUSD:", responce, "") -# Get my number +# Get my number (only works on some networks) def getmynumber(): - responce = command("+CUSD=1,\"*#100#\"", 8000, "+CUSD:") - responcedata = extractval("+CUSD:", responce, "").split(",") + responcedata = ussd("*#100#", 8000).split(",") if (len(responcedata)>=2): return responcedata[1].strip().strip("\"") else: return "" -# Turn on the SIM800 -poweron() + + + +# Start turning on the SIM800 +poweron(True) + From 1657999162d48abfb2472f5bf0486236b5c03a18 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Wed, 22 Aug 2018 13:49:57 +0100 Subject: [PATCH 03/24] Fixing stupid filename typo. --- lib/{sim800 => sim800.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/{sim800 => sim800.py} (100%) diff --git a/lib/sim800 b/lib/sim800.py similarity index 100% rename from lib/sim800 rename to lib/sim800.py From beec1705bcd4e6ef52d935e6ba9be6d828f2c241 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Wed, 22 Aug 2018 15:12:12 +0100 Subject: [PATCH 04/24] Finished call, sms and basic functionality --- lib/sim800.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/lib/sim800.py b/lib/sim800.py index 70c3375..4528e15 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -30,13 +30,21 @@ def ispositive(responce): def isnegative(responce): return (responce=="NO CARRIER") or (responce=="ERROR") or (responce=="NO DIALTONE") or (responce=="BUSY") or (responce=="NO ANSWER") -# Extract the [first] parameter from a responce +# Extract the [first/only] parameter from a responce def extractval(parameter, responce, default=""): for entry in responce: if entry.startswith(parameter): return (entry[len(parameter):]).strip() return default +# Extract all parameter from a responce +def extractvals(parameter, responce): + result = [] + for entry in responce: + if entry.startswith(parameter): + result.append((entry[len(parameter):]).strip()) + return result + # Read a lines of responce from the UART def readline(): stringin = "" @@ -168,11 +176,32 @@ def sendsms(number, message): command("AT+CMGS=\"" + str(number) + "\"") return command(message + "\x1a", 60000) +# List the summery of SMS messages (0=unread,1=read,2=saved unread,3=saved sent, 4=all) +def listsms(stat=0): + statvals = ["REC UNREAD", "REC READ", "STO UNSENT", "STO SENT", "ALL"] + return extractvals("+CMGL:", command("AT+CMGL=\"" + statvals[stat] + "\",1", 8000)) + +# Check if we have recived a new unread SMS message +def newsms(): + return len(listsms(stat=0))>0 + +# Read an SMS message +def readsms(index, leaveunread=False): + responce = command("AT+CMGR=" + str(index) + "," + str(int(leaveunread)), 5000) + if (len(responce)>=3): + return responce[-2] + else: + return "" + +# Delete an SMS message +def deletesms(index): + command("AT+CMGD=" + str(index), 5000) + # Get the IMEI number of the SIM800 def imei(): responce = command("AT+GSN") if (len(responce)>=2): - return responce[len(responce)-2] + return responce[-2] else: return "" @@ -194,6 +223,29 @@ def speakervolume(level=None): responce = command("AT+CLVL?") return int(extractval("+CLVL:", responce, 0)) +# Get/Set/Preview and set the ringtone (alert is 0-19) +def ringtone(alert=None,preview=False): + # Set/preview the new ringtone if we have one to set + if alert is not None: + command("AT+CALS=" + str(alert) + "," + str(int(preview))) + # Retieve the current/new setting + responce = command("AT+CALS?") + current = extractval("+CALS:", responce, 0).split(",")[0] + # Stop the preview unless we started it + if alert is None: + command("AT+CALS=" + current + ",0") + # Return the surrent setting + return int(current) + +# play a tone though the SIM800 (MHz and ms) +def playtone(freq=0,duration=2000,async=True): + if freq>0: + command("AT+SIMTONE=1," + str(freq) + "," + str(duration) + ",0," + str(duration)) + if not async: + time.sleep(duration/1000) + else: + command("AT+SIMTONE=0") + # Is the battery charging (0=no, 1=yes, 2=full) def batterycharging(): responce = command("AT+CBC") @@ -205,7 +257,7 @@ def batterycharge(): responce = command("AT+CBC") vals = extractval("+CBC:", responce, "0,0,0").split(",") return int(vals[1]) - + # List the available operator (returns list of [0=?,1=available,2=current,3=forbidden], 0=long name, 1=short name, 2=GSMLAI ) def listoperators(available_only=True): delim = "||||" From 77fa55bb959f18943c13ea9a6e64b04516ad9bc0 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Wed, 22 Aug 2018 15:53:57 +0100 Subject: [PATCH 05/24] Added Bluetooth power, status and name/address --- lib/sim800.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/lib/sim800.py b/lib/sim800.py index 4528e15..0328dc3 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -295,7 +295,6 @@ def soperator(mode, format=None, operator=None): # Get the activity status (returns 0=ready, 2=unknown, 3=ringing, 4=call in progress) def getstatus(): responce = command("AT+CPAS") - vals = extractval("+CPAS:", responce, "2") return int(extractval("+CPAS:", responce, "2")) # Request Unstructured Supplementary Service Data from network @@ -311,10 +310,45 @@ def getmynumber(): else: return "" +# Turn on or off Bluetooth +def btpower(onoroff=True): + command("AT+BTPOWER=" + str(int(onoroff)), 8000) +# Turn on Bluetooth +def btpoweron(): + btpower(True); +# Turn off Bluetooth +def btpoweroff(): + btpower(False); +# Get the current status of Bluetooth (0=off,5=idel, other values docuemtned for "AT+BTSTATUS") +def btstatus(): + responce = command("AT+BTSTATUS?") + return int(extractval("+BTSTATUS:", responce, "0")) +# Is Bluetooth on? +def btison(): + return btstatus()>=5 + +# Get/Set the Bluetooth host device name +def btname(name=None): + if name is not None: + responce = command("AT+BTHOST=" + str(name)) + # Retrieve the current name + responce = command("AT+BTHOST?") + responcedata = extractval("+BTHOST:", responce, "").split(",") + return responcedata[0] + +# Get the Bluetooth address +def btaddress(): + responce = command("AT+BTHOST?") + responcedata = extractval("+BTHOST:", responce, "").split(",") + if (len(responcedata)>=2): + return responcedata[-1] + else: + return "" + # Start turning on the SIM800 poweron(True) From 816f62ae96e7a9a2786f2224e9796619c82e679d Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Wed, 22 Aug 2018 16:02:32 +0100 Subject: [PATCH 06/24] Added getfirmwarever() --- lib/sim800.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/sim800.py b/lib/sim800.py index 0328dc3..2cb4663 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -297,6 +297,14 @@ def getstatus(): responce = command("AT+CPAS") return int(extractval("+CPAS:", responce, "2")) +# Get the firmware revision +def getfirmwarever(): + responce = command("AT+CGMR") + if (len(responce)>=3): + return responce[-2] + else: + return "" + # Request Unstructured Supplementary Service Data from network def ussd(ussdstring, timeout=8000): responce = command("AT+CUSD=1,\"" + ussdstring + "\"", timeout, "+CUSD:") From ff81bbca946920b8f5fc007b8c15967226c8cf52 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Wed, 22 Aug 2018 16:18:59 +0100 Subject: [PATCH 07/24] Quick SMS fix Force text mode on all SMS request just in case someone is issuing direct +CMGF commands from an app. --- lib/sim800.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/sim800.py b/lib/sim800.py index 2cb4663..0c3a382 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -179,6 +179,9 @@ def sendsms(number, message): # List the summery of SMS messages (0=unread,1=read,2=saved unread,3=saved sent, 4=all) def listsms(stat=0): statvals = ["REC UNREAD", "REC READ", "STO UNSENT", "STO SENT", "ALL"] + # Swith to text mode + command("AT+CMGF=1") + # Retrieve the list return extractvals("+CMGL:", command("AT+CMGL=\"" + statvals[stat] + "\",1", 8000)) # Check if we have recived a new unread SMS message @@ -187,6 +190,11 @@ def newsms(): # Read an SMS message def readsms(index, leaveunread=False): + # Swith to text mode + command("AT+CMGF=1") + # Switch to ASCII(ish) + command("AT+CSCS=\"8859-1\"") + # Retrieve the message responce = command("AT+CMGR=" + str(index) + "," + str(int(leaveunread)), 5000) if (len(responce)>=3): return responce[-2] From 965c36e439937101f967a02a497764d6a07981f4 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Wed, 22 Aug 2018 23:51:11 +0100 Subject: [PATCH 08/24] Quick typo fix --- lib/sim800.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/sim800.py b/lib/sim800.py index 0c3a382..cc44608 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -291,8 +291,8 @@ def currentoperator(format=0): else: return "" -# Set the operator selection ([0=automatic,2=deregister]) -def soperator(mode, format=None, operator=None): +# Set the operator selection ([0=automatic,1=Manual,2=deregister,4=try manual then automatic]) +def setoperator(mode, format=None, operator=None): params = "" if format is not None: params += "," + str(format) From 2904a08d5ef1f61c1ac9f1279a8a680f5787a930 Mon Sep 17 00:00:00 2001 From: Sam Machin Date: Fri, 24 Aug 2018 08:57:59 +0100 Subject: [PATCH 09/24] Added function to get the IMSI Getting the IMSI is useful for trying to debug issues with peoples subscriptions in the GSM netwotk and its not printed on the card or anywhere, if the card isn't inserted or hasn't started properly then the command returns ERROR --- lib/sim800.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/sim800.py b/lib/sim800.py index cc44608..f1a7e2d 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -213,6 +213,14 @@ def imei(): else: return "" +# Get the IMSI number of the Sim Card +def imsi(): + responce = command("AT+CIMI") + if (len(responce)>=2): + return responce[-2] + else: + return "" + # Get/Set ringer volume (0-100) def ringervolume(level=None): # Set the new leve if we have one to set From f25369fdffdbe0f7105513c2ae162debf5a95f46 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Fri, 24 Aug 2018 12:57:25 +0100 Subject: [PATCH 10/24] More Bluetooth added and other tidying --- lib/sim800.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/sim800.py b/lib/sim800.py index f1a7e2d..c478dcb 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -6,12 +6,12 @@ uart_default_baud = 115200 uart_timeout = 28 default_responce_timeout = 2000 -status_pin = Pin(6, Pin.IN) -ringer_pin = Pin(8, Pin.IN) -pwr_key_pin = Pin(23, Pin.OUT) +status_pin = machine.Pin(6, machine.Pin.IN) +ringer_pin = machine.Pin(8, machine.Pin.IN) +pwr_key_pin = machine.Pin(23, machine.Pin.OUT) # Open the UART -uart = machine.UART(uart_port, uart_default_baud, mode=UART.BINARY, timeout=uart_timeout) +uart = machine.UART(uart_port, uart_default_baud, mode=machine.UART.BINARY, timeout=uart_timeout) dirtybuffer = False # Flag if the buffer could have residual end of responces line in it? # Check if the SIM800 is powered up @@ -372,7 +372,11 @@ def btaddress(): return responcedata[-1] else: return "" - + +# Get the Bluetooth address (timeout from 10000 to 60000, returnd device ID, name, address, rssi) +def btscan(timeout=30000): + responce = command("AT+BTSCAN=1," + str(int(timeout/1000)), timeout+8000, "+BTSCAN: 1") + return extractvals("+BTSCAN: 0,", responce) + # Start turning on the SIM800 poweron(True) - From ab91fcece0b22d0af608aa10845121e935eff311 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Fri, 24 Aug 2018 15:03:24 +0100 Subject: [PATCH 11/24] Bluetooth pairing and connection --- lib/sim800.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/lib/sim800.py b/lib/sim800.py index c478dcb..54af72b 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -378,5 +378,45 @@ def btscan(timeout=30000): responce = command("AT+BTSCAN=1," + str(int(timeout/1000)), timeout+8000, "+BTSCAN: 1") return extractvals("+BTSCAN: 0,", responce) +# Pair a Bluetooth device +def btpair(device): + return command("AT+BTPAIR=0," + str(device), 8000) + +# Confirm the pairing of a Bluetooth device +def btpairconfirm(passkey=None): + if passkey is None: + return command("AT+BTPAIR=1,1", 8000) + else: + return command("AT+BTPAIR=2," + str(passkey), 8000) + +# Cancel/reject the pairing of a Bluetooth device +def btpairreject(): + return command("AT+BTPAIR=1,0", 8000) + +# Unpair a Bluetooth device (unpair everything when device is 0) +def btunpair(device=0): + return command("AT+BTUNPAIR=" + str(device), 8000) + +# List the paired Bluetooth devices +def btpaired(): + responce = command("AT+BTSTATUS?") + return extractvals("P:", responce) + +# Connect a Bluetooth device +def btconnect(device, profile): + responce = command("AT+BTCONNECT=" + str(device) + "," + str(profile), 8000) + return extractvals("+BTCONNECT:", responce) + +# Connect a Bluetooth device +def btdisconnect(device): + responce = command("AT+BTCONNECT=" + str(device), 8000) + return extractvals("+BTDISCONN:", responce) + +# List the Bluetooth connections +def btconnected(): + responce = command("AT+BTSTATUS?") + return extractvals("C:", responce) + + # Start turning on the SIM800 poweron(True) From 6490ebb8851610b31d39c386d86de945a65ce7df Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Sat, 25 Aug 2018 10:25:58 +0100 Subject: [PATCH 12/24] More Bluetooth functionality --- lib/sim800.py | 80 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 5 deletions(-) diff --git a/lib/sim800.py b/lib/sim800.py index 54af72b..64ba0fd 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -149,7 +149,7 @@ def call(number): def answer(): command("ATA", 20000) -# End a voice call +# End/reject a voice call def hangup(): command("ATH") @@ -373,6 +373,15 @@ def btaddress(): else: return "" +# Get/Set Bluetooth visibility +def btvisible(visible=None): + # Set the new leve if we have one to set + if visible is not None: + command("AT+BTVIS=" + str(visible)) + # Retieve the set gain to report back + responce = command("AT+BTVIS?") + return int(extractval("+BTVIS:", responce, 0)) + # Get the Bluetooth address (timeout from 10000 to 60000, returnd device ID, name, address, rssi) def btscan(timeout=30000): responce = command("AT+BTSCAN=1," + str(int(timeout/1000)), timeout+8000, "+BTSCAN: 1") @@ -380,7 +389,8 @@ def btscan(timeout=30000): # Pair a Bluetooth device def btpair(device): - return command("AT+BTPAIR=0," + str(device), 8000) + responce = command("AT+BTPAIR=0," + str(device), 8000, "+BTPAIRING:") + return extractval("+BTPAIRING:", responce, "").split(",") # Confirm the pairing of a Bluetooth device def btpairconfirm(passkey=None): @@ -402,14 +412,26 @@ def btpaired(): responce = command("AT+BTSTATUS?") return extractvals("P:", responce) +# List profiles supported by a paired device +def btgetprofiles(device): + responce = command("AT+BTGETPROF=" + str(device), 8000) + responcelist = extractvals("+BTGETPROF:", responce) + results = [] + for entry in responcelist: + subresults = [] + for subentry in entry.split(","): + subresults.append(subentry.strip("\"")) + results.append(subresults) + return results + # Connect a Bluetooth device def btconnect(device, profile): - responce = command("AT+BTCONNECT=" + str(device) + "," + str(profile), 8000) + responce = command("AT+BTCONNECT=" + str(device) + "," + str(profile), 8000, "+BTCONNECT:") return extractvals("+BTCONNECT:", responce) -# Connect a Bluetooth device +# Disconnect a Bluetooth device def btdisconnect(device): - responce = command("AT+BTCONNECT=" + str(device), 8000) + responce = command("AT+BTDISCONN=" + str(device), 8000, "+BTDISCONN:") return extractvals("+BTDISCONN:", responce) # List the Bluetooth connections @@ -417,6 +439,54 @@ def btconnected(): responce = command("AT+BTSTATUS?") return extractvals("C:", responce) +# Make a voice call +def btcall(number): + command("AT+BTATD" + str(number), 20000) +# Answer a voice call +def btanswer(): + command("AT+BTATA", 20000) + +# End a voice call +def bthangup(): + command("AT+BTATH") + +# Redial the last number +def btredial(): + command("AT+BTATDL") + +# Play DTMF tone(s) on a Bluetooth call +def btdtmf(number): + validdigits = '1234567890#*ABCD' + for digit in str(number).upper(): + if (digit in validdigits): + command("AT+BTVTS=" + digit) + elif (digit==','): + time.sleep(1) + +# Get/Set Bluetooth voice gain (0-15) +def btvoicevolume(gain=None): + # Set the new leve if we have one to set + if gain is not None: + command("AT+BTVGS=" + str(gain)) + # Retieve the set gain to report back + responce = command("AT+BTVGS?") + return int(extractval("+BTVGS:", responce, 0)) + +# Get/Set microphone gain volume (0-15) +def btvoicevolume(gain=None): + # Set the new leve if we have one to set + if gain is not None: + command("AT+BTVGM=" + str(gain)) + # Retieve the set gain to report back + responce = command("AT+BTVGM?") + return int(extractval("+BTVGM:", responce, 0)) + +# Get the Bluetooth signal quality for a device (-127-0) +def btrssi(device): + responce = command("AT+BTRSSI=" + str(device)) + return int(extractval("+BTRSSI:", responce, 0)) + + # Start turning on the SIM800 poweron(True) From 77680a45c62f1d33fa72b18485f13b2011e0eea7 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Sun, 26 Aug 2018 12:11:11 +0100 Subject: [PATCH 13/24] Filesystem access added --- lib/sim800.py | 86 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 10 deletions(-) diff --git a/lib/sim800.py b/lib/sim800.py index 64ba0fd..74630c9 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -24,11 +24,18 @@ def isringing(): # Identify if this was a positive responce def ispositive(responce): - return (responce=="OK") or responce.startswith("CONNECT") or responce.startswith("> ") + return (responce=="OK") or responce.startswith("CONNECT") or responce.startswith("SEND OK") # Identify if this was a negative responce def isnegative(responce): - return (responce=="NO CARRIER") or (responce=="ERROR") or (responce=="NO DIALTONE") or (responce=="BUSY") or (responce=="NO ANSWER") + return (responce=="NO CARRIER") or (responce=="ERROR") or (responce=="NO DIALTONE") or (responce=="BUSY") or (responce=="NO ANSWER") or (responce=="SEND FAIL") or (responce=="TIMEOUT") or (responce=="TimeOut") + +# Identify if this is the completion of a responce +def isdefinitive(responce, custom=None): + if custom is not None: + return ispositive(responce) or isnegative(responce) or responce.startswith(custom) + else: + return ispositive(responce) or isnegative(responce) # Extract the [first/only] parameter from a responce def extractval(parameter, responce, default=""): @@ -62,8 +69,8 @@ def readline(): stringin += str(charin, "ASCII") # Execute a command on the module -# command is the AT command without the AT or CR/LF, responce_timeout (in ms) is how long to wait for completion, custom_endofdata is to wait for a non standard bit of -def command(command="AT", responce_timeout=default_responce_timeout, custom_endofdata=None): +# command is the AT command without the AT or CR/LF, responce_timeout (in ms) is how long to wait for completion, required_responce is to wait for a non standard responce, custom_endofdata will finish when found +def command(command="AT", responce_timeout=default_responce_timeout, required_responce=None, custom_endofdata=None): global dirtybuffer # Empty the buffer uart.read() @@ -76,7 +83,7 @@ def command(command="AT", responce_timeout=default_responce_timeout, custom_endo # Read the results result = [] complete = False - customcomplete = custom_endofdata is None + customcomplete = required_responce is None timeouttime = time.time()+(responce_timeout/1000) while (time.time()0): result.append(line) # Check if we have a standard end of responce - if (ispositive(line)) or (isnegative(line)): + if isdefinitive(line, custom_endofdata): complete = True # Check if we have the data we are looking for - if (custom_endofdata is not None) and (line.startswith(custom_endofdata)): + if (required_responce is not None) and (line.startswith(required_responce)): customcomplete = True # Check if we are done if complete and customcomplete: return result # We ran out of time # set the dirty buffer flag is an out of date end of responcs cound end up in the buffer - if custom_endofdata is None: + if required_responce is None: dirtybuffer = True result.append("TIMEOUT") return result @@ -173,7 +180,7 @@ def sendsms(number, message): # Switch to ASCII(ish) command("AT+CSCS=\"8859-1\"") # Send the message - command("AT+CMGS=\"" + str(number) + "\"") + command("AT+CMGS=\"" + str(number) + "\"", 2000, None, "> ") return command(message + "\x1a", 60000) # List the summery of SMS messages (0=unread,1=read,2=saved unread,3=saved sent, 4=all) @@ -487,6 +494,65 @@ def btrssi(device): responce = command("AT+BTRSSI=" + str(device)) return int(extractval("+BTRSSI:", responce, 0)) - + + + +# Get available space on the flash storage +def fsfree(): + responce = command("AT+FSMEM") + return extractval("+FSMEM:", responce, "?:0bytes").split(",")[0].split(":")[1][:-5] + +# List the entries in directory on flash storage (returned directories end with "\\") +def fsls(directory=""): + if not directory.endswith("\\"): + directory += "\\" + return command("AT+FSLS=" + str(directory))[1:-1] + +# Get the size of a file on the flash storage +def fssize(filename): + responce = command("AT+FSFLSIZE=" + str(filename)) + return int(extractval("+FSFLSIZE:", responce, "-1")) + +# Create a directory on flash storage +def fsmkdir(directory): + return ispositive(command("AT+FSMKDIR=" + str(directory))[-1]) + +# Remove a directory on flash storage +def fsrmdir(directory): + return ispositive(command("AT+FSRMDIR=" + str(directory))[-1]) + +# Create a file on flash storage +def fscreate(filename): + return ispositive(command("AT+FSCREATE=" + str(filename))[-1]) + +# Read data from a file on the flash storage +def fsread(filename, size=1024, start=0): + mode=int(start>0) + return command("AT+FSREAD=" + str(filename) + "," + str(mode) + "," + str(size) + "," + str(start))[1:-1] + +# Write data to a file on the flash storage +def fswrite(filename, data, append=True, truncate=False): + if truncate or (fssize(filename)<0): + fscreate(filename) + responce = command("AT+FSWRITE=" + str(filename) + "," + str(int(append)) + "," + str(len(data)) + ",8", 2000, None, ">") + if responce[-1].startswith(">"): + return ispositive(command(data)[-1]) + else: + return False + +# Delete a file from flash storage +def fsrm(filename): + return ispositive(command("AT+FSDEL=" + str(filename))[-1]) + +# Rename a file on the flash storage +def fsmv(filenamefrom, filenameto): + return ispositive(command("AT+FSRENAME=" + str(filenamefrom) + "," + str(filenameto))[-1]) + + # Start turning on the SIM800 poweron(True) + +# Testing code to move to app +#tilda.Buttons.enable_interrupt(tilda.Buttons.BTN_Call, answer()) +#tilda.Buttons.enable_interrupt(tilda.Buttons.BTN_End, hangup()) + From 655af11946a3e0b21ccc24d390f8b83d2437bbd3 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Sun, 26 Aug 2018 12:20:33 +0100 Subject: [PATCH 14/24] Correcting Spelling --- lib/sim800.py | 204 +++++++++++++++++++++++++------------------------- 1 file changed, 102 insertions(+), 102 deletions(-) diff --git a/lib/sim800.py b/lib/sim800.py index 74630c9..251cd18 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -4,7 +4,7 @@ import time uart_port = 1 uart_default_baud = 115200 uart_timeout = 28 -default_responce_timeout = 2000 +default_response_timeout = 2000 status_pin = machine.Pin(6, machine.Pin.IN) ringer_pin = machine.Pin(8, machine.Pin.IN) @@ -12,7 +12,7 @@ pwr_key_pin = machine.Pin(23, machine.Pin.OUT) # Open the UART uart = machine.UART(uart_port, uart_default_baud, mode=machine.UART.BINARY, timeout=uart_timeout) -dirtybuffer = False # Flag if the buffer could have residual end of responces line in it? +dirtybuffer = False # Flag if the buffer could have residual end of reresponsesponces line in it? # Check if the SIM800 is powered up def ison(): @@ -22,37 +22,37 @@ def ison(): def isringing(): return ringer_pin.value()==0 -# Identify if this was a positive responce -def ispositive(responce): - return (responce=="OK") or responce.startswith("CONNECT") or responce.startswith("SEND OK") +# Identify if this was a positive response +def ispositive(response): + return (response=="OK") or response.startswith("CONNECT") or response.startswith("SEND OK") -# Identify if this was a negative responce -def isnegative(responce): - return (responce=="NO CARRIER") or (responce=="ERROR") or (responce=="NO DIALTONE") or (responce=="BUSY") or (responce=="NO ANSWER") or (responce=="SEND FAIL") or (responce=="TIMEOUT") or (responce=="TimeOut") +# Identify if this was a negative response +def isnegative(response): + return (response=="NO CARRIER") or (response=="ERROR") or (response=="NO DIALTONE") or (response=="BUSY") or (response=="NO ANSWER") or (response=="SEND FAIL") or (response=="TIMEOUT") or (response=="TimeOut") -# Identify if this is the completion of a responce -def isdefinitive(responce, custom=None): +# Identify if this is the completion of a response +def isdefinitive(response, custom=None): if custom is not None: - return ispositive(responce) or isnegative(responce) or responce.startswith(custom) + return ispositive(response) or isnegative(response) or response.startswith(custom) else: - return ispositive(responce) or isnegative(responce) + return ispositive(response) or isnegative(response) -# Extract the [first/only] parameter from a responce -def extractval(parameter, responce, default=""): - for entry in responce: +# Extract the [first/only] parameter from a response +def extractval(parameter, response, default=""): + for entry in response: if entry.startswith(parameter): return (entry[len(parameter):]).strip() return default -# Extract all parameter from a responce -def extractvals(parameter, responce): +# Extract all parameter from a response +def extractvals(parameter, response): result = [] - for entry in responce: + for entry in response: if entry.startswith(parameter): result.append((entry[len(parameter):]).strip()) return result -# Read a lines of responce from the UART +# Read a lines of response from the UART def readline(): stringin = "" while (True): @@ -69,8 +69,8 @@ def readline(): stringin += str(charin, "ASCII") # Execute a command on the module -# command is the AT command without the AT or CR/LF, responce_timeout (in ms) is how long to wait for completion, required_responce is to wait for a non standard responce, custom_endofdata will finish when found -def command(command="AT", responce_timeout=default_responce_timeout, required_responce=None, custom_endofdata=None): +# command is the AT command without the AT or CR/LF, response_timeout (in ms) is how long to wait for completion, required_response is to wait for a non standard response, custom_endofdata will finish when found +def command(command="AT", response_timeout=default_response_timeout, required_response=None, custom_endofdata=None): global dirtybuffer # Empty the buffer uart.read() @@ -83,25 +83,25 @@ def command(command="AT", responce_timeout=default_responce_timeout, required_re # Read the results result = [] complete = False - customcomplete = required_responce is None - timeouttime = time.time()+(responce_timeout/1000) + customcomplete = required_response is None + timeouttime = time.time()+(response_timeout/1000) while (time.time()0): result.append(line) - # Check if we have a standard end of responce + # Check if we have a standard end of response if isdefinitive(line, custom_endofdata): complete = True # Check if we have the data we are looking for - if (required_responce is not None) and (line.startswith(required_responce)): + if (required_response is not None) and (line.startswith(required_response)): customcomplete = True # Check if we are done if complete and customcomplete: return result # We ran out of time # set the dirty buffer flag is an out of date end of responcs cound end up in the buffer - if required_responce is None: + if required_response is None: dirtybuffer = True result.append("TIMEOUT") return result @@ -202,9 +202,9 @@ def readsms(index, leaveunread=False): # Switch to ASCII(ish) command("AT+CSCS=\"8859-1\"") # Retrieve the message - responce = command("AT+CMGR=" + str(index) + "," + str(int(leaveunread)), 5000) - if (len(responce)>=3): - return responce[-2] + response = command("AT+CMGR=" + str(index) + "," + str(int(leaveunread)), 5000) + if (len(response)>=3): + return response[-2] else: return "" @@ -214,17 +214,17 @@ def deletesms(index): # Get the IMEI number of the SIM800 def imei(): - responce = command("AT+GSN") - if (len(responce)>=2): - return responce[-2] + response = command("AT+GSN") + if (len(response)>=2): + return response[-2] else: return "" # Get the IMSI number of the Sim Card def imsi(): - responce = command("AT+CIMI") - if (len(responce)>=2): - return responce[-2] + response = command("AT+CIMI") + if (len(response)>=2): + return response[-2] else: return "" @@ -234,8 +234,8 @@ def ringervolume(level=None): if level is not None: command("AT+CRSL=" + str(level)) # Retieve the set level to report back - responce = command("AT+CRSL?") - return int(extractval("+CRSL:", responce, 0)) + response = command("AT+CRSL?") + return int(extractval("+CRSL:", response, 0)) # Get/Set speaker volume (0-100) def speakervolume(level=None): @@ -243,8 +243,8 @@ def speakervolume(level=None): if level is not None: command("AT+CLVL=" + str(level)) # Retieve the set level to report back - responce = command("AT+CLVL?") - return int(extractval("+CLVL:", responce, 0)) + response = command("AT+CLVL?") + return int(extractval("+CLVL:", response, 0)) # Get/Set/Preview and set the ringtone (alert is 0-19) def ringtone(alert=None,preview=False): @@ -252,8 +252,8 @@ def ringtone(alert=None,preview=False): if alert is not None: command("AT+CALS=" + str(alert) + "," + str(int(preview))) # Retieve the current/new setting - responce = command("AT+CALS?") - current = extractval("+CALS:", responce, 0).split(",")[0] + response = command("AT+CALS?") + current = extractval("+CALS:", response, 0).split(",")[0] # Stop the preview unless we started it if alert is None: command("AT+CALS=" + current + ",0") @@ -271,24 +271,24 @@ def playtone(freq=0,duration=2000,async=True): # Is the battery charging (0=no, 1=yes, 2=full) def batterycharging(): - responce = command("AT+CBC") - vals = extractval("+CBC:", responce, "0,0,0").split(",") + response = command("AT+CBC") + vals = extractval("+CBC:", response, "0,0,0").split(",") return int(vals[0]) # How full is the battery (1-100) def batterycharge(): - responce = command("AT+CBC") - vals = extractval("+CBC:", responce, "0,0,0").split(",") + response = command("AT+CBC") + vals = extractval("+CBC:", response, "0,0,0").split(",") return int(vals[1]) # List the available operator (returns list of [0=?,1=available,2=current,3=forbidden], 0=long name, 1=short name, 2=GSMLAI ) def listoperators(available_only=True): delim = "||||" - responce = command("AT+COPS=?", 45000) - responcedata = extractval("+COPS:", responce, "").split(",,")[0] - responcelist = responcedata.replace("),(",delim)[1:-1].split(delim) + response = command("AT+COPS=?", 45000) + responsedata = extractval("+COPS:", response, "").split(",,")[0] + responselist = responsedata.replace("),(",delim)[1:-1].split(delim) results = [] - for entry in responcelist: + for entry in responselist: subresults = [] for subentry in entry.split(","): subresults.append(subentry.strip("\"")) @@ -299,10 +299,10 @@ def listoperators(available_only=True): # Get the current operator (format 0=long name, 1=short name, 2=GSMLAI) def currentoperator(format=0): command("AT+COPS=3," + str(format)) - responce = command("AT+COPS?") - responcedata = extractval("+COPS:", responce, "").split(",") - if (len(responcedata)>=3): - return responcedata[2].strip("\"") + response = command("AT+COPS?") + responsedata = extractval("+COPS:", response, "").split(",") + if (len(responsedata)>=3): + return responsedata[2].strip("\"") else: return "" @@ -317,27 +317,27 @@ def setoperator(mode, format=None, operator=None): # Get the activity status (returns 0=ready, 2=unknown, 3=ringing, 4=call in progress) def getstatus(): - responce = command("AT+CPAS") - return int(extractval("+CPAS:", responce, "2")) + response = command("AT+CPAS") + return int(extractval("+CPAS:", response, "2")) # Get the firmware revision def getfirmwarever(): - responce = command("AT+CGMR") - if (len(responce)>=3): - return responce[-2] + response = command("AT+CGMR") + if (len(response)>=3): + return response[-2] else: return "" # Request Unstructured Supplementary Service Data from network def ussd(ussdstring, timeout=8000): - responce = command("AT+CUSD=1,\"" + ussdstring + "\"", timeout, "+CUSD:") - return extractval("+CUSD:", responce, "") + response = command("AT+CUSD=1,\"" + ussdstring + "\"", timeout, "+CUSD:") + return extractval("+CUSD:", response, "") # Get my number (only works on some networks) def getmynumber(): - responcedata = ussd("*#100#", 8000).split(",") - if (len(responcedata)>=2): - return responcedata[1].strip().strip("\"") + responsedata = ussd("*#100#", 8000).split(",") + if (len(responsedata)>=2): + return responsedata[1].strip().strip("\"") else: return "" @@ -355,8 +355,8 @@ def btpoweroff(): # Get the current status of Bluetooth (0=off,5=idel, other values docuemtned for "AT+BTSTATUS") def btstatus(): - responce = command("AT+BTSTATUS?") - return int(extractval("+BTSTATUS:", responce, "0")) + response = command("AT+BTSTATUS?") + return int(extractval("+BTSTATUS:", response, "0")) # Is Bluetooth on? def btison(): @@ -365,18 +365,18 @@ def btison(): # Get/Set the Bluetooth host device name def btname(name=None): if name is not None: - responce = command("AT+BTHOST=" + str(name)) + response = command("AT+BTHOST=" + str(name)) # Retrieve the current name - responce = command("AT+BTHOST?") - responcedata = extractval("+BTHOST:", responce, "").split(",") - return responcedata[0] + response = command("AT+BTHOST?") + responsedata = extractval("+BTHOST:", response, "").split(",") + return responsedata[0] # Get the Bluetooth address def btaddress(): - responce = command("AT+BTHOST?") - responcedata = extractval("+BTHOST:", responce, "").split(",") - if (len(responcedata)>=2): - return responcedata[-1] + response = command("AT+BTHOST?") + responsedata = extractval("+BTHOST:", response, "").split(",") + if (len(responsedata)>=2): + return responsedata[-1] else: return "" @@ -386,18 +386,18 @@ def btvisible(visible=None): if visible is not None: command("AT+BTVIS=" + str(visible)) # Retieve the set gain to report back - responce = command("AT+BTVIS?") - return int(extractval("+BTVIS:", responce, 0)) + response = command("AT+BTVIS?") + return int(extractval("+BTVIS:", response, 0)) # Get the Bluetooth address (timeout from 10000 to 60000, returnd device ID, name, address, rssi) def btscan(timeout=30000): - responce = command("AT+BTSCAN=1," + str(int(timeout/1000)), timeout+8000, "+BTSCAN: 1") - return extractvals("+BTSCAN: 0,", responce) + response = command("AT+BTSCAN=1," + str(int(timeout/1000)), timeout+8000, "+BTSCAN: 1") + return extractvals("+BTSCAN: 0,", response) # Pair a Bluetooth device def btpair(device): - responce = command("AT+BTPAIR=0," + str(device), 8000, "+BTPAIRING:") - return extractval("+BTPAIRING:", responce, "").split(",") + response = command("AT+BTPAIR=0," + str(device), 8000, "+BTPAIRING:") + return extractval("+BTPAIRING:", response, "").split(",") # Confirm the pairing of a Bluetooth device def btpairconfirm(passkey=None): @@ -416,15 +416,15 @@ def btunpair(device=0): # List the paired Bluetooth devices def btpaired(): - responce = command("AT+BTSTATUS?") - return extractvals("P:", responce) + response = command("AT+BTSTATUS?") + return extractvals("P:", response) # List profiles supported by a paired device def btgetprofiles(device): - responce = command("AT+BTGETPROF=" + str(device), 8000) - responcelist = extractvals("+BTGETPROF:", responce) + response = command("AT+BTGETPROF=" + str(device), 8000) + responselist = extractvals("+BTGETPROF:", response) results = [] - for entry in responcelist: + for entry in responselist: subresults = [] for subentry in entry.split(","): subresults.append(subentry.strip("\"")) @@ -433,18 +433,18 @@ def btgetprofiles(device): # Connect a Bluetooth device def btconnect(device, profile): - responce = command("AT+BTCONNECT=" + str(device) + "," + str(profile), 8000, "+BTCONNECT:") - return extractvals("+BTCONNECT:", responce) + response = command("AT+BTCONNECT=" + str(device) + "," + str(profile), 8000, "+BTCONNECT:") + return extractvals("+BTCONNECT:", response) # Disconnect a Bluetooth device def btdisconnect(device): - responce = command("AT+BTDISCONN=" + str(device), 8000, "+BTDISCONN:") - return extractvals("+BTDISCONN:", responce) + response = command("AT+BTDISCONN=" + str(device), 8000, "+BTDISCONN:") + return extractvals("+BTDISCONN:", response) # List the Bluetooth connections def btconnected(): - responce = command("AT+BTSTATUS?") - return extractvals("C:", responce) + response = command("AT+BTSTATUS?") + return extractvals("C:", response) # Make a voice call def btcall(number): @@ -477,8 +477,8 @@ def btvoicevolume(gain=None): if gain is not None: command("AT+BTVGS=" + str(gain)) # Retieve the set gain to report back - responce = command("AT+BTVGS?") - return int(extractval("+BTVGS:", responce, 0)) + response = command("AT+BTVGS?") + return int(extractval("+BTVGS:", response, 0)) # Get/Set microphone gain volume (0-15) def btvoicevolume(gain=None): @@ -486,21 +486,21 @@ def btvoicevolume(gain=None): if gain is not None: command("AT+BTVGM=" + str(gain)) # Retieve the set gain to report back - responce = command("AT+BTVGM?") - return int(extractval("+BTVGM:", responce, 0)) + response = command("AT+BTVGM?") + return int(extractval("+BTVGM:", response, 0)) # Get the Bluetooth signal quality for a device (-127-0) def btrssi(device): - responce = command("AT+BTRSSI=" + str(device)) - return int(extractval("+BTRSSI:", responce, 0)) + response = command("AT+BTRSSI=" + str(device)) + return int(extractval("+BTRSSI:", response, 0)) # Get available space on the flash storage def fsfree(): - responce = command("AT+FSMEM") - return extractval("+FSMEM:", responce, "?:0bytes").split(",")[0].split(":")[1][:-5] + response = command("AT+FSMEM") + return extractval("+FSMEM:", response, "?:0bytes").split(",")[0].split(":")[1][:-5] # List the entries in directory on flash storage (returned directories end with "\\") def fsls(directory=""): @@ -510,8 +510,8 @@ def fsls(directory=""): # Get the size of a file on the flash storage def fssize(filename): - responce = command("AT+FSFLSIZE=" + str(filename)) - return int(extractval("+FSFLSIZE:", responce, "-1")) + response = command("AT+FSFLSIZE=" + str(filename)) + return int(extractval("+FSFLSIZE:", response, "-1")) # Create a directory on flash storage def fsmkdir(directory): @@ -534,8 +534,8 @@ def fsread(filename, size=1024, start=0): def fswrite(filename, data, append=True, truncate=False): if truncate or (fssize(filename)<0): fscreate(filename) - responce = command("AT+FSWRITE=" + str(filename) + "," + str(int(append)) + "," + str(len(data)) + ",8", 2000, None, ">") - if responce[-1].startswith(">"): + response = command("AT+FSWRITE=" + str(filename) + "," + str(int(append)) + "," + str(len(data)) + ",8", 2000, None, ">") + if response[-1].startswith(">"): return ispositive(command(data)[-1]) else: return False From 41a2fa37dd78b8c3351a3051ffd236fe3a073c9f Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Sun, 26 Aug 2018 17:37:19 +0100 Subject: [PATCH 15/24] Callback for ringing, CLI and SMS added. Tidied turn on and command processes for better asynchronous startup. --- lib/sim800.py | 90 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 18 deletions(-) diff --git a/lib/sim800.py b/lib/sim800.py index 251cd18..529067c 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -1,19 +1,26 @@ import machine import time +import micropython + +CALLER_CALLBACK = 1 +CLI_CALLBACK = 2 +NEWSMS_CALLBACK = 3 uart_port = 1 uart_default_baud = 115200 uart_timeout = 28 default_response_timeout = 2000 -status_pin = machine.Pin(6, machine.Pin.IN) -ringer_pin = machine.Pin(8, machine.Pin.IN) -pwr_key_pin = machine.Pin(23, machine.Pin.OUT) +status_pin = machine.Pin(machine.Pin.GPIO_SIM_STATUS, machine.Pin.IN) +ringer_pin = machine.Pin(machine.Pin.GPIO_SIM_RI, machine.Pin.IN) +pwr_key_pin = machine.Pin(machine.Pin.GPIO_SIM_PWR_KEY, machine.Pin.OUT) # Open the UART uart = machine.UART(uart_port, uart_default_baud, mode=machine.UART.BINARY, timeout=uart_timeout) dirtybuffer = False # Flag if the buffer could have residual end of reresponsesponces line in it? +callbacks = [] + # Check if the SIM800 is powered up def ison(): return status_pin.value()==1 @@ -22,6 +29,22 @@ def ison(): def isringing(): return ringer_pin.value()==0 +# Register for a callback +def registercallback(call, function): + callbacks.append([call, function]) + +# Deregitser for a callback +def deregistercallback(function): + for entry in callbacks: + if entry[1]==function: + callbacks.remove(entry) + +# Execute external funtion of a callback +def callcallback(call, parameter): + for entry in callbacks: + if entry[0]==call: + micropython.schedule(entry[1], parameter) + # Identify if this was a positive response def ispositive(response): return (response=="OK") or response.startswith("CONNECT") or response.startswith("SEND OK") @@ -68,16 +91,31 @@ def readline(): elif not (charin == b'\n'): stringin += str(charin, "ASCII") +def processcallbacks(line): + # Check for ringing + if line=="RING": + callcallback(CALLER_CALLBACK, "") + # Check for new caler lin identification + val = extractval("+CLIP:", [line]) + if val: + callcallback(CLI_CALLBACK, val) + # Check for new SMS messages + val = extractval("+CMT:", [line]) + if val: + callcallback(NEWSMS_CALLBACK, val) + +# Process the buffer for unsolicited result codes +def processbuffer(): + while uart.any()>0: + line = readline() + processcallbacks(line) + # Execute a command on the module -# command is the AT command without the AT or CR/LF, response_timeout (in ms) is how long to wait for completion, required_response is to wait for a non standard response, custom_endofdata will finish when found -def command(command="AT", response_timeout=default_response_timeout, required_response=None, custom_endofdata=None): +# The same interface as and called by command() but without power so can be called from power() +def command_internal(command="AT", response_timeout=default_response_timeout, required_response=None, custom_endofdata=None): global dirtybuffer - # Empty the buffer - uart.read() - # Do not bother if we are powered off - if not ison(): - dirtybuffer = False - return ["POWERED OFF"] + # Process anything remaining in the buffer + processbuffer() # Send the command uart.write(command + "\r") # Read the results @@ -87,6 +125,7 @@ def command(command="AT", response_timeout=default_response_timeout, required_re timeouttime = time.time()+(response_timeout/1000) while (time.time()0): result.append(line) @@ -123,20 +162,25 @@ def power(onoroff, async): # Stop pressing the virtual key pwr_key_pin.off() # Clear the buffer - uart.read() + processbuffer() dirtybuffer = False - # Send a command to autonigotiate UART speed + # We are now live if isonnow: - command("AT") + # Send a command to autonigotiate UART speed + command_internal("AT") + # Turn on new SMS notificationn + command_internal("AT+CNMI=1,2") + # Turn on calling line identification notification + command_internal("AT+CLIP=1") return isonnow # Power on the SIM800 (returns true when on) def poweron(async=False): - power(True, async) + return power(True, async) # Power off the SIM800 def poweroff(async=False): - power(False, async) + return power(False, async) # Change the speed on the communication def uartspeed(newbaud): @@ -148,6 +192,13 @@ def uartspeed(newbaud): else: uart = machine.UART(uart_port, newbaud, mode=UART.BINARY, timeout=uart_timeout) +# command is the AT command without the AT or CR/LF, response_timeout (in ms) is how long to wait for completion, required_response is to wait for a non standard response, custom_endofdata will finish when found +def command(command="AT", response_timeout=default_response_timeout, required_response=None, custom_endofdata=None): + # Check we are powered on and set up + poweron(False) + # Call the internal command() function + return command_internal(command, response_timeout, required_response, custom_endofdata) + # Make a voice call def call(number): command("ATD" + str(number) + ";", 20000) @@ -550,9 +601,12 @@ def fsmv(filenamefrom, filenameto): # Start turning on the SIM800 -poweron(True) +onatstart = poweron(True) # Testing code to move to app +# Try using call and end buttons to answer and hangup #tilda.Buttons.enable_interrupt(tilda.Buttons.BTN_Call, answer()) #tilda.Buttons.enable_interrupt(tilda.Buttons.BTN_End, hangup()) - +# See if the netowrk list can be used for checking SIM800 +#status_pin = machine.Pin(7, machine.Pin.IN) +#tilda.Buttons.enable_interrupt(machine.Pin(machine.Pin.GPIO_SIM_NETLIGHT, machine.Pin.IN),processbuffer()) From aad89b4777d5ad6d357295afef58481c4327865b Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Sun, 26 Aug 2018 18:40:01 +0100 Subject: [PATCH 16/24] Added more engineering and debug functions --- lib/sim800.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/lib/sim800.py b/lib/sim800.py index 529067c..1ecb3d8 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -1,3 +1,8 @@ +"""SIM800 library for the TiLDA MK4""" + +___license___ = "MIT" +___dependencies___ = [] + import machine import time import micropython @@ -279,6 +284,36 @@ def imsi(): else: return "" +# Get the ICCID of the Sim Card +def iccid(): + response = command("AT+ICCID") + return extractval("+ICCID:", response) + + # Get the received signal strength indication +def rssi(): + response = command("AT+CSQ") + return int(extractval("+CSQ:", response, "0,0").split(",")[0]) + +# Get the bit error rate +def ber(): + response = command("AT+CSQ") + return int(extractval("+CSQ:", response, "0,0").split(",")[1]) + +# Get the cell engineering information (True to include neighboring cell id) +def engineeringinfo(neighbor=False): + command("AT+CENG=1," + str(int(neighbor))) + response = command("AT+CENG?") + command("AT+CENG=0") + responselist = extractvals("+CENG:", response)[1:] + results = [] + for entry in responselist: + results.append([entry[0], entry.replace("\"", "").split(",")[1:]]) + return results + +# Get the cell id of the currently connected cell +def cellid(): + return engineeringinfo()[0][1][6] + # Get/Set ringer volume (0-100) def ringervolume(level=None): # Set the new leve if we have one to set From 03400de0dba52f73eda7fcb4258afa1d383aa4a0 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Sun, 26 Aug 2018 20:57:26 +0100 Subject: [PATCH 17/24] Binary file transfer and audio recording --- lib/sim800.py | 76 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/lib/sim800.py b/lib/sim800.py index 1ecb3d8..08d4afa 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -10,6 +10,7 @@ import micropython CALLER_CALLBACK = 1 CLI_CALLBACK = 2 NEWSMS_CALLBACK = 3 +RECORD_CALLBACK = 4 uart_port = 1 uart_default_baud = 115200 @@ -346,7 +347,7 @@ def ringtone(alert=None,preview=False): # Return the surrent setting return int(current) -# play a tone though the SIM800 (MHz and ms) +# Play a tone though the SIM800 (MHz and ms) def playtone(freq=0,duration=2000,async=True): if freq>0: command("AT+SIMTONE=1," + str(freq) + "," + str(duration) + ",0," + str(duration)) @@ -355,6 +356,39 @@ def playtone(freq=0,duration=2000,async=True): else: command("AT+SIMTONE=0") +# Record audio (id=1-10) +def startrecording(id=1, length=None): + if length is None: + return ispositive(command("AT+CREC=1," + str(id) + ",0")[-1]) + else: + return ispositive(command("AT+CREC=1," + str(id) + ",0," + str(int(length/1000)))[-1]) + +# Stop recording audio +def stoprecording(): + return ispositive(command("AT+CREC=2")[-1]) + +# Delete recording +def deleterecording(id=1): + return ispositive(command("AT+CREC=3," + str(id))[-1]) + +# Play recording +def startplayback(id=1, channel=0, level=100, repeat=False): + return ispositive(command("AT+CREC=4," + str(id) + "," + str(channel) + "," + str(level) + "," + str(int(repeat)))[-1]) + +# Stop playback +def stopplayback(): + return ispositive(command("AT+CREC=5")[-1]) + +# List recordings (returns a list of ids and size) +def listrecordings(): + response = command("AT+CREC=7") + responselist = extractvals("+CREC:", response) + result = [] + for entry in responselist: + splitentry = entry.split(",") + result.append([splitentry[1], splitentry[2]]) + return result + # Is the battery charging (0=no, 1=yes, 2=full) def batterycharging(): response = command("AT+CBC") @@ -611,21 +645,47 @@ def fsrmdir(directory): def fscreate(filename): return ispositive(command("AT+FSCREATE=" + str(filename))[-1]) -# Read data from a file on the flash storage +# Read text data from a file on the flash storage def fsread(filename, size=1024, start=0): mode=int(start>0) - return command("AT+FSREAD=" + str(filename) + "," + str(mode) + "," + str(size) + "," + str(start))[1:-1] + command() + request = "AT+FSREAD=" + str(filename) + "," + str(mode) + "," + str(size) + "," + str(start) + "\n" + uart.write(request) + data = uart.read() + while True: + time.sleep(uart_timeout/1000) + if uart.any()==0: + break + data += uart.read() + if not data.endswith("ERROR\r\n"): + return data[len(request)+2:-6] + else: + return None -# Write data to a file on the flash storage -def fswrite(filename, data, append=True, truncate=False): - if truncate or (fssize(filename)<0): - fscreate(filename) - response = command("AT+FSWRITE=" + str(filename) + "," + str(int(append)) + "," + str(len(data)) + ",8", 2000, None, ">") +# Append a small chunk data to a file on the flash storage, you should use sfwrite +def fswritepart(filename, data): + response = command("AT+FSWRITE=" + str(filename) + ",1," + str(len(data)) + ",8", 2000, None, ">") if response[-1].startswith(">"): return ispositive(command(data)[-1]) else: return False +# Write data to a file on the flash storage +def fswrite(filename, data, truncate=False): + length = len(data) + pointer = 0 + chunksize = 256 + # Create a file if needed + if truncate or (fssize(filename)<0): + fscreate(filename) + # Loop through the data in small chunks + while pointer0: @@ -178,15 +173,21 @@ def power(onoroff, async): command_internal("AT+CNMI=1,2") # Turn on calling line identification notification command_internal("AT+CLIP=1") + # Swith to text mode + command("AT+CMGF=1") + # Switch to ASCII(ish) + command("AT+CSCS=\"8859-1\"") + # Enable DTMF detection + command("AT+DDET=1,0,1") return isonnow # Power on the SIM800 (returns true when on) def poweron(async=False): return power(True, async) -# Power off the SIM800 +# Power off the SIM800 (returns true when off) def poweroff(async=False): - return power(False, async) + return not power(False, async) # Change the speed on the communication def uartspeed(newbaud): @@ -221,6 +222,12 @@ def hangup(): def redial(): command("ATDL") +# Get the current/latest number to call +def latestnumber(): + if ison(): + processbuffer() + return clip.split(",")[0].strip("\"") + # Play DTMF tone(s) on a call def dtmf(number): validdigits = '1234567890#*ABCD' @@ -457,7 +464,9 @@ def ussd(ussdstring, timeout=8000): def getmynumber(): responsedata = ussd("*#100#", 8000).split(",") if (len(responsedata)>=2): - return responsedata[1].strip().strip("\"") + num = responsedata[1].strip().strip("\"") + if num.isdigit()>0: + return num else: return "" @@ -500,8 +509,11 @@ def btaddress(): else: return "" -# Get/Set Bluetooth visibility +# Get/Set Bluetooth visibility (True for on, False for off) def btvisible(visible=None): + # Power on if we want to be visible + if visibie: + btpoweron() # Set the new leve if we have one to set if visible is not None: command("AT+BTVIS=" + str(visible)) @@ -511,6 +523,7 @@ def btvisible(visible=None): # Get the Bluetooth address (timeout from 10000 to 60000, returnd device ID, name, address, rssi) def btscan(timeout=30000): + btpoweron() response = command("AT+BTSCAN=1," + str(int(timeout/1000)), timeout+8000, "+BTSCAN: 1") return extractvals("+BTSCAN: 0,", response) @@ -568,18 +581,22 @@ def btconnected(): # Make a voice call def btcall(number): + btpoweron() command("AT+BTATD" + str(number), 20000) # Answer a voice call def btanswer(): + btpoweron() command("AT+BTATA", 20000) # End a voice call def bthangup(): + btpoweron() command("AT+BTATH") # Redial the last number def btredial(): + btpoweron() command("AT+BTATDL") # Play DTMF tone(s) on a Bluetooth call @@ -614,7 +631,7 @@ def btrssi(device): response = command("AT+BTRSSI=" + str(device)) return int(extractval("+BTRSSI:", response, 0)) - +# xxxy - Add BT object transfer, serial, handsfree # Get available space on the flash storage @@ -645,8 +662,8 @@ def fsrmdir(directory): def fscreate(filename): return ispositive(command("AT+FSCREATE=" + str(filename))[-1]) -# Read text data from a file on the flash storage -def fsread(filename, size=1024, start=0): +# Read a chunk of data from a file on the flash storage +def fsreadpart(filename, size=256, start=0): mode=int(start>0) command() request = "AT+FSREAD=" + str(filename) + "," + str(mode) + "," + str(size) + "," + str(start) + "\n" @@ -662,6 +679,16 @@ def fsread(filename, size=1024, start=0): else: return None +# Read data from a file on the flash storage +def fsread(filename): + result = bytearray(0) + while True: + chunk = fsreadpart(filename, 256, len(result)) + if chunk is not None: + result += chunk + else: + return result + # Append a small chunk data to a file on the flash storage, you should use sfwrite def fswritepart(filename, data): response = command("AT+FSWRITE=" + str(filename) + ",1," + str(len(data)) + ",8", 2000, None, ">") From 2293aeb5f383c659b96abfda82b5373c52930ea9 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Mon, 27 Aug 2018 23:06:23 +0100 Subject: [PATCH 19/24] OPP transfer and other Bluetooth tidying --- lib/sim800.py | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/lib/sim800.py b/lib/sim800.py index fab8788..f38cfbd 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -297,7 +297,7 @@ def iccid(): response = command("AT+ICCID") return extractval("+ICCID:", response) - # Get the received signal strength indication +# Get the received signal strength indication def rssi(): response = command("AT+CSQ") return int(extractval("+CSQ:", response, "0,0").split(",")[0]) @@ -558,12 +558,21 @@ def btgetprofiles(device): responselist = extractvals("+BTGETPROF:", response) results = [] for entry in responselist: - subresults = [] - for subentry in entry.split(","): - subresults.append(subentry.strip("\"")) + splitentry = entry.split(",") + subresults = [int(splitentry[0]), splitentry[1].strip("\"")] results.append(subresults) return results - + +# Is a paticula profile supported (usename=False for id, True for name) +def btprofilesupported(device, usename): + profiles = btgetprofiles(device) + for entry in profiles: + if (type(usename)==int) and (entry[0]==usename): + return True + if (type(usename)==str) and (entry[1]==usename): + return True + return False + # Connect a Bluetooth device def btconnect(device, profile): response = command("AT+BTCONNECT=" + str(device) + "," + str(profile), 8000, "+BTCONNECT:") @@ -579,6 +588,22 @@ def btconnected(): response = command("AT+BTSTATUS?") return extractvals("C:", response) +# Push an OPP object/file over Bluetooth (must be paired for OPP, monitor +BTOPPPUSH: for sucsess / fail / server issue) +def btopppush(device, filename): + response = command("AT+BTOPPPUSH=" + str(device) + "," + filename, 45000, "+BTOPPPUSH:") + responce2 = extractval("+BTOPPPUSH:", response, "") + return responce2=="1" + +# Accept an OPP object/file from Bluetooth (monitor +BTOPPPUSHING: for offering, files stored in "\\User\\BtReceived") +def btoppaccept(): + response = command("AT+BTOPPACPT=1", 45000, "+BTOPPPUSH:") + responce2 = extractval("+BTOPPPUSH:", response, 0) + return responce2=="1" + +# Reject an OPP object/file transfer +def btoppreject(): + command("AT+BTOPPACPT=0") + # Make a voice call def btcall(number): btpoweron() @@ -631,6 +656,9 @@ def btrssi(device): response = command("AT+BTRSSI=" + str(device)) return int(extractval("+BTRSSI:", response, 0)) + + + # xxxy - Add BT object transfer, serial, handsfree From eb3ff46b88d4efbcf769be4d937f43ea92922ce2 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Mon, 27 Aug 2018 23:41:33 +0100 Subject: [PATCH 20/24] Bluetooth SSP Note that is needs testing. --- lib/sim800.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/sim800.py b/lib/sim800.py index f38cfbd..bd91a84 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -179,6 +179,9 @@ def power(onoroff, async): command("AT+CSCS=\"8859-1\"") # Enable DTMF detection command("AT+DDET=1,0,1") + # Set up multichannel Bluetooth without transparent transmition + command("AT+BTSPPCFG=\"MC\",1") + command("AT+BTSPPCFG=\"TT\",0") return isonnow # Power on the SIM800 (returns true when on) @@ -600,6 +603,30 @@ def btoppaccept(): responce2 = extractval("+BTOPPPUSH:", response, 0) return responce2=="1" +# Send data over a Bluetooth serial connection +def btsppwrite(connection, data): + response = command("AT+BTSPPSEND=" + str(connection) + "," + str(len(data)), 8000, None, ">") + if response[-1].startswith(">"): + return ispositive(command(data)[-1]) + else: + return False + +# Receive data from a Bluetooth serial connection +def btsppread(connection): + command() + request = "AT+BTSPPGET=3," + str(connection) + "\n" + uart.write(request) + data = uart.read() + while True: + time.sleep(uart_timeout/1000) + if uart.any()==0: + break + data += uart.read() + if not data.endswith("ERROR\r\n"): + return data[len(request)+2:-6] + else: + return None + # Reject an OPP object/file transfer def btoppreject(): command("AT+BTOPPACPT=0") From fbc2d1a34814adb2a4e72797c0c24ec55a45c892 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Tue, 28 Aug 2018 12:56:18 +0100 Subject: [PATCH 21/24] Final tweaks or tidying --- lib/sim800.py | 80 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/lib/sim800.py b/lib/sim800.py index bd91a84..6372777 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -20,8 +20,12 @@ pwr_key_pin = machine.Pin(machine.Pin.GPIO_SIM_PWR_KEY, machine.Pin.OUT) uart = machine.UART(uart_port, uart_default_baud, mode=machine.UART.BINARY, timeout=uart_timeout) dirtybuffer = False # Flag if the buffer could have residual end of reresponsesponces line in it? +# A list of callback functions callbacks = [] + +# Globals for remembering callback data clip = "" +btpairing = '' # Check if the SIM800 is powered up def ison(): @@ -86,20 +90,17 @@ def readline(): # This will be part of the string then elif not (charin == b'\n'): stringin += str(charin, "ASCII") - -# xxxy -RING_CALLBACK = "RING" -CLIP_CALLBACK = "+CLIP:" -NEWSMS_CALLBACK = "+CMT:" -DTMF_CALLBACK = "+DTMF:" -BTPAIRING_CALLBACK = "+BTPAIRING:" # Check if we have a callback hook for this line def processcallbacks(line): global clip - # check for the caller line information + global btpairing + # Check for the caller line information if line.startswith("+CLIP:"): clip = line[6:].strip() + # Check for Bluetooth pairing request + if line.startswith("+BTPAIRING:"): + btpairing = line[11:].strip() # Check for app callbacks for entry in callbacks: if line.startswith(entry[0]): @@ -515,28 +516,52 @@ def btaddress(): # Get/Set Bluetooth visibility (True for on, False for off) def btvisible(visible=None): # Power on if we want to be visible - if visibie: + if visible: btpoweron() # Set the new leve if we have one to set if visible is not None: - command("AT+BTVIS=" + str(visible)) + command("AT+BTVIS=" + str(int(visible))) # Retieve the set gain to report back response = command("AT+BTVIS?") return int(extractval("+BTVIS:", response, 0)) -# Get the Bluetooth address (timeout from 10000 to 60000, returnd device ID, name, address, rssi) +# Get the Bluetooth address (timeout in ms from 10000 to 60000, returnd device ID, name, address, rssi) def btscan(timeout=30000): btpoweron() + result = [] response = command("AT+BTSCAN=1," + str(int(timeout/1000)), timeout+8000, "+BTSCAN: 1") - return extractvals("+BTSCAN: 0,", response) + for entry in extractvals("+BTSCAN: 0,", response): + splitentry = entry.split(",") + result = [int(splitentry[0]), splitentry[1].strip("\""), splitentry[2], int(splitentry[3])] + return result +# Get the requesting paring device name +def btparingname(): + if ison(): + processbuffer() + return btpairing.split(",")[0].strip("\"") + +# Get the requesting paring passcode +def btparingpasscode(): + if ison(): + processbuffer() + splitdata = btpairing.split(",") + if (len(splitdata)>=3): + return splitdata[2] + else: + return "" + # Pair a Bluetooth device def btpair(device): + global btpairing + btpairing = '' response = command("AT+BTPAIR=0," + str(device), 8000, "+BTPAIRING:") return extractval("+BTPAIRING:", response, "").split(",") # Confirm the pairing of a Bluetooth device def btpairconfirm(passkey=None): + global btpairing + btpairing = '' if passkey is None: return command("AT+BTPAIR=1,1", 8000) else: @@ -544,6 +569,8 @@ def btpairconfirm(passkey=None): # Cancel/reject the pairing of a Bluetooth device def btpairreject(): + global btpairing + btpairing = '' return command("AT+BTPAIR=1,0", 8000) # Unpair a Bluetooth device (unpair everything when device is 0) @@ -552,8 +579,12 @@ def btunpair(device=0): # List the paired Bluetooth devices def btpaired(): + result = [] response = command("AT+BTSTATUS?") - return extractvals("P:", response) + for entry in extractvals("P:", response): + splitentry = entry.split(",") + result = [int(splitentry[0]), splitentry[1].strip("\""), splitentry[2]] + return result # List profiles supported by a paired device def btgetprofiles(device): @@ -588,8 +619,12 @@ def btdisconnect(device): # List the Bluetooth connections def btconnected(): + result = [] response = command("AT+BTSTATUS?") - return extractvals("C:", response) + for entry in extractvals("C:", response): + splitentry = entry.split(",") + result = [int(splitentry[0]), splitentry[1].strip("\""), splitentry[2]] + return result # Push an OPP object/file over Bluetooth (must be paired for OPP, monitor +BTOPPPUSH: for sucsess / fail / server issue) def btopppush(device, filename): @@ -683,12 +718,6 @@ def btrssi(device): response = command("AT+BTRSSI=" + str(device)) return int(extractval("+BTRSSI:", response, 0)) - - - -# xxxy - Add BT object transfer, serial, handsfree - - # Get available space on the flash storage def fsfree(): response = command("AT+FSMEM") @@ -776,14 +805,5 @@ def fsrm(filename): def fsmv(filenamefrom, filenameto): return ispositive(command("AT+FSRENAME=" + str(filenamefrom) + "," + str(filenameto))[-1]) - -# Start turning on the SIM800 +# Start turning on the SIM800 asynchronously onatstart = poweron(True) - -# Testing code to move to app -# Try using call and end buttons to answer and hangup -#tilda.Buttons.enable_interrupt(tilda.Buttons.BTN_Call, answer()) -#tilda.Buttons.enable_interrupt(tilda.Buttons.BTN_End, hangup()) -# See if the netowrk list can be used for checking SIM800 -#status_pin = machine.Pin(7, machine.Pin.IN) -#tilda.Buttons.enable_interrupt(machine.Pin(machine.Pin.GPIO_SIM_NETLIGHT, machine.Pin.IN),processbuffer()) From 319f45bb1106b6fbab0ea208e961f0444828a6c1 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Tue, 28 Aug 2018 19:23:02 +0100 Subject: [PATCH 22/24] Minor bug fixes Venerable name changes for compatibility with new. Turn on amp. Worked around SMS unicode translation issue. --- lib/sim800.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/sim800.py b/lib/sim800.py index 6372777..289ecab 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -15,6 +15,7 @@ default_response_timeout = 2000 status_pin = machine.Pin(machine.Pin.GPIO_SIM_STATUS, machine.Pin.IN) ringer_pin = machine.Pin(machine.Pin.GPIO_SIM_RI, machine.Pin.IN) pwr_key_pin = machine.Pin(machine.Pin.GPIO_SIM_PWR_KEY, machine.Pin.OUT) +amp_pin = machine.Pin(machine.Pin.GPIO_AMP_SHUTDOWN, machine.Pin.OUT) # Open the UART uart = machine.UART(uart_port, uart_default_baud, mode=machine.UART.BINARY, timeout=uart_timeout) @@ -89,7 +90,7 @@ def readline(): return stringin # This will be part of the string then elif not (charin == b'\n'): - stringin += str(charin, "ASCII") + stringin += chr(ord(charin)) # Check if we have a callback hook for this line def processcallbacks(line): @@ -148,15 +149,15 @@ def command_internal(command="AT", response_timeout=default_response_timeout, re return result # Power on the SIM800 (True=on, False=off, returns true when on) -def power(onoroff, async): +def power(onoroff, asyncro): # Get to a stable state if not async - if not async and pwr_key_pin.value(): + if not asyncro and pwr_key_pin.value(): pwr_key_pin.off() time.sleep(3) # Press the virtual power key if we are off if not (ison()==onoroff): pwr_key_pin.on() - if not async: + if not asyncro: time.sleep(3) # Have we just turned on? isonnow = ison() @@ -186,12 +187,12 @@ def power(onoroff, async): return isonnow # Power on the SIM800 (returns true when on) -def poweron(async=False): - return power(True, async) +def poweron(asyncro=False): + return power(True, asyncro) # Power off the SIM800 (returns true when off) -def poweroff(async=False): - return not power(False, async) +def poweroff(asyncro=False): + return not power(False, asyncro) # Change the speed on the communication def uartspeed(newbaud): @@ -359,10 +360,10 @@ def ringtone(alert=None,preview=False): return int(current) # Play a tone though the SIM800 (MHz and ms) -def playtone(freq=0,duration=2000,async=True): +def playtone(freq=0,duration=2000,asyncro=True): if freq>0: command("AT+SIMTONE=1," + str(freq) + "," + str(duration) + ",0," + str(duration)) - if not async: + if not asyncro: time.sleep(duration/1000) else: command("AT+SIMTONE=0") @@ -807,3 +808,6 @@ def fsmv(filenamefrom, filenameto): # Start turning on the SIM800 asynchronously onatstart = poweron(True) + +# Turn on the audio amp +amp_pin.on() From be1032ffd26490632489111595605e44f0876774 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Tue, 28 Aug 2018 20:08:41 +0100 Subject: [PATCH 23/24] Added interrupt for monitoring uart and buttons. --- lib/sim800.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/sim800.py b/lib/sim800.py index 289ecab..913228d 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -16,6 +16,7 @@ status_pin = machine.Pin(machine.Pin.GPIO_SIM_STATUS, machine.Pin.IN) ringer_pin = machine.Pin(machine.Pin.GPIO_SIM_RI, machine.Pin.IN) pwr_key_pin = machine.Pin(machine.Pin.GPIO_SIM_PWR_KEY, machine.Pin.OUT) amp_pin = machine.Pin(machine.Pin.GPIO_AMP_SHUTDOWN, machine.Pin.OUT) +netlight_pin = machine.Pin(machine.Pin.GPIO_SIM_NETLIGHT, machine.Pin.IN) # Open the UART uart = machine.UART(uart_port, uart_default_baud, mode=machine.UART.BINARY, timeout=uart_timeout) @@ -204,7 +205,18 @@ def uartspeed(newbaud): else: uart = machine.UART(uart_port, newbaud, mode=UART.BINARY, timeout=uart_timeout) -# command is the AT command without the AT or CR/LF, response_timeout (in ms) is how long to wait for completion, required_response is to wait for a non standard response, custom_endofdata will finish when found +# Netlight IRQ (called for finishing setartup and polling uart) +def netlightscheduled_internal(nullparam=None): + # Finish powerup procedure if needed + poweron() + # Check for incomming commands + processbuffer() + +# Netlight IRQ (called for finishing setartup and polling uart) +def netlightirq_internal(nullparam=None): + micropython.schedule(netlightscheduled_internal, None) + +# Command is the AT command without the AT or CR/LF, response_timeout (in ms) is how long to wait for completion, required_response is to wait for a non standard response, custom_endofdata will finish when found def command(command="AT", response_timeout=default_response_timeout, required_response=None, custom_endofdata=None): # Check we are powered on and set up poweron(False) @@ -806,8 +818,25 @@ def fsrm(filename): def fsmv(filenamefrom, filenameto): return ispositive(command("AT+FSRENAME=" + str(filenamefrom) + "," + str(filenameto))[-1]) +# Callback for call buton being pressed +def callbuttonpressed_internal(nullparam=None): + answer() + +# Callback for end buton being pressed +def endbuttonpressed_internal(nullparam=None): + hangup() + +# Startup... + # Start turning on the SIM800 asynchronously onatstart = poweron(True) # Turn on the audio amp amp_pin.on() + +# Enable the interupt on network light for polling uart +netlight_pin.irq(netlightirq_internal) + +# Hook in the Call / End buttons +tilda.Buttons.enable_interrupt(tilda.Buttons.BTN_Call, callbuttonpressed_internal) +tilda.Buttons.enable_interrupt(tilda.Buttons.BTN_End, endbuttonpressed_internal) From 9b7afad5c9737948f0b5bd176007c1ced98f8fc1 Mon Sep 17 00:00:00 2001 From: Alistair MacDonald Date: Tue, 28 Aug 2018 20:51:02 +0100 Subject: [PATCH 24/24] Bug fixing Small issue with interrupts and loading when SIM800 is already on. --- lib/sim800.py | 64 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/lib/sim800.py b/lib/sim800.py index 913228d..9f7d2ea 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -6,6 +6,7 @@ ___dependencies___ = [] import machine import time import micropython +import tilda uart_port = 1 uart_default_baud = 115200 @@ -149,6 +150,24 @@ def command_internal(command="AT", response_timeout=default_response_timeout, re result.append("TIMEOUT") return result +# Send the command to set the default configuration to the SIM800 +def senddefaultconfig(): + # Send a command to autonigotiate UART speed + command_internal("AT") + # Turn on new SMS notificationn + command_internal("AT+CNMI=1,2") + # Turn on calling line identification notification + command_internal("AT+CLIP=1") + # Swith to text mode + command("AT+CMGF=1") + # Switch to ASCII(ish) + command("AT+CSCS=\"8859-1\"") + # Enable DTMF detection + command("AT+DDET=1,0,1") + # Set up multichannel Bluetooth without transparent transmition + command("AT+BTSPPCFG=\"MC\",1") + command("AT+BTSPPCFG=\"TT\",0") + # Power on the SIM800 (True=on, False=off, returns true when on) def power(onoroff, asyncro): # Get to a stable state if not async @@ -170,21 +189,8 @@ def power(onoroff, asyncro): dirtybuffer = False # We are now live if isonnow: - # Send a command to autonigotiate UART speed - command_internal("AT") - # Turn on new SMS notificationn - command_internal("AT+CNMI=1,2") - # Turn on calling line identification notification - command_internal("AT+CLIP=1") - # Swith to text mode - command("AT+CMGF=1") - # Switch to ASCII(ish) - command("AT+CSCS=\"8859-1\"") - # Enable DTMF detection - command("AT+DDET=1,0,1") - # Set up multichannel Bluetooth without transparent transmition - command("AT+BTSPPCFG=\"MC\",1") - command("AT+BTSPPCFG=\"TT\",0") + # Set the deault configuration + senddefaultconfig() return isonnow # Power on the SIM800 (returns true when on) @@ -205,17 +211,18 @@ def uartspeed(newbaud): else: uart = machine.UART(uart_port, newbaud, mode=UART.BINARY, timeout=uart_timeout) -# Netlight IRQ (called for finishing setartup and polling uart) -def netlightscheduled_internal(nullparam=None): - # Finish powerup procedure if needed - poweron() +# Netlight sheduled (called for polling uart) +def netlightscheduled_internal(pinstate): + # Complete the setup procedure if needed + if pwr_key_pin.value() and ison(): + poweron() # Check for incomming commands processbuffer() -# Netlight IRQ (called for finishing setartup and polling uart) -def netlightirq_internal(nullparam=None): - micropython.schedule(netlightscheduled_internal, None) - +# Netlight IRQ (called for polling uart) +def netlightirq_internal(pinstate): + micropython.schedule(netlightscheduled_internal, pinstate) + # Command is the AT command without the AT or CR/LF, response_timeout (in ms) is how long to wait for completion, required_response is to wait for a non standard response, custom_endofdata will finish when found def command(command="AT", response_timeout=default_response_timeout, required_response=None, custom_endofdata=None): # Check we are powered on and set up @@ -828,15 +835,20 @@ def endbuttonpressed_internal(nullparam=None): # Startup... + # Start turning on the SIM800 asynchronously onatstart = poweron(True) +# Reset SIM800 configuration if hardware is still on from before +if onatstart: + senddefaultconfig() + # Turn on the audio amp amp_pin.on() -# Enable the interupt on network light for polling uart -netlight_pin.irq(netlightirq_internal) - # Hook in the Call / End buttons tilda.Buttons.enable_interrupt(tilda.Buttons.BTN_Call, callbuttonpressed_internal) tilda.Buttons.enable_interrupt(tilda.Buttons.BTN_End, endbuttonpressed_internal) + +# Enable the interupts on network light to poll uart +netlight_pin.irq(netlightirq_internal)