I had to change the default PINs of over 200 SIM cards once. And such a situation could arise again. So I've built a PIN changer in which I just have to insert the card, wait a few seconds and it's done.
The Card Slots
SIM cards in their natural form factor aren't as fiddly to handle as they are in the form factors most people know, which is Mini SIM, Micro SIM and Nano SIM. Classical SIM cards are the same size as other smart cards. I found a card slot with an end switch on eBay. I like it when I find industry grade parts for cheap on eBay as part of some remaining stock. Additionally I've used a slide-in mini SIM slot and another, separate end switch from my parts collection in case I to change the PINs of smaller cards.
The Baseband Processor
Other parts that I've used is an Arduino Nano sized Arduino Nano nearly-clone and an A6 modem module. There are many similar modem modules designed around different but similar ICs. Many of them are cheap and widely used for DIY IoT projects. So example code for the Arduino and other help can be easily found on the web. I don't know why I went with a module with an A6. But it works fine and there are an Arduino library for it as well as cheap modem modules with it available.
(tba:voltage supply)
The Controller
Yes, Arduino may be kind of the noob go-to board and could look up how to use microcontrollers on their own finally and even if I don't want to I could finally start to use ESP32s like everybody else. But I know Arduinos and by now I'm familiar with it and it works, so, whatever. Arduino Nano is kind of my go-to form factor now because they have integraded USB, are Uno compatible and small. Unless I need more or something very specific I use Arduino Nano almost-clones with USB-C port.
The Code
The code is a real mess. It had been a long time since I had written any even halfway serious C. It may have been the first time, actually. The sketch surely is very easily improved by somebody who knows what they are doing. I intended to improve it myself. But the project is currently abandoned and The code is doing what it should in a way I initially had in mind as the goal. But I'll leave the mess of the comments in for the case that somebody wants to make out what I was thinking.
// Funktionen umschreiben: Beim Empfangen wird erwartet: 1. der AT-Command zurück, 2. eine Antwort, 3. OK oder ein ERROR.
// Daher sollte abgefragt werden, bis entweder OK oder ERROR kam oder 20/50/9001(?) Abfragen lang weder OK noch ERROR an kam.
// Die Antwort in Variable speichern? Naja, String zurückgeben halt.
// Antworten, die mit "^" anfangen brauchen nicht gehandlet zu werden, da keine Kommandos, die mit AT^ beginnen gesendet werden.
#include
SoftwareSerial A6MODULE(6,7);
int intled = 13; // Internal LED
int successled = 8; // Green LED
int failled = 9; // Red LED
int wrongpinled = 10; // Orange LED
boolean debug=true;
//String commands[5] = { "AT", "AT+CPINC2", "AT+CPIN?", "AT+CLCK=\"SC\",2", "AT+CPIN=\"3010\"" };
//int command = 0;
const byte maxmsglength = 32;
char received[maxmsglength];
boolean newData = false;
String response = "";
int i=0;
/*
To check/do:
1 AT: OK?
2 is PUK required - abort
3 are less than 3 PIN attempts left? - abort AT^CPINC=?
4 is PIN disabled
5 enable it: 0000
6 is PIN enabled
7 unlock
8 is card unlocked
9 change PIN: 1996
was PIN wrong - report and leave it
*/
void setup() {
pinMode(intled, OUTPUT);
pinMode(successled, OUTPUT);
pinMode(failled, OUTPUT);
pinMode(wrongpinled, OUTPUT);
// All LEDs turn on at the beginning and stay on during the wait period at the beginning, then turn off before communication with the A6 module starts.
digitalWrite(intled, HIGH);
digitalWrite(successled, HIGH);
digitalWrite(failled, HIGH);
digitalWrite(wrongpinled, HIGH);
Serial.begin(9600);
delay(500);
digitalWrite(intled, LOW);
digitalWrite(successled, LOW);
digitalWrite(failled, LOW);
digitalWrite(wrongpinled, LOW);
A6MODULE.begin(9600);
delay(500);
digitalWrite(intled, LOW);
}
void loop() {
sendtoa6("AT");
if(getfroma6("OK")) {
// sendtoa6("AT+CPIN?");
// if(getfroma6("+CPIN:SIM PUK")) { fail; } // If the required password is PUK, abort.
// sendtoa6("AT+CPIN?");
// if(getfroma6("+CPIN:SIM PIN2")) { fail; } // If the required password is PIN2, abort.
// sendtoa6("AT+CPIN?");
// if(getfroma6("+CPIN:SIM PUK2")) { fail; } // If the required password is PUK2, abort.
// WAIT FOR SWITCH RELEASE FIRST
sendtoa6("AT+CPIN?");
getfroma6("+CPIN:SIM PIN"); // The last non-empty response will be stored in the global response variable. Problem with this: If the A6 module sends an unsolicitated message before the response to the CPIN command, nothing gets done and the card needs to be re-inserted again.
if(strcmp(response, "OK") == 0) { // If already no PIN is required
// sendtoa6("AT+CLCK=\"SC\",2"); // Ist PIN-Abfrage eingeschaltet? Oder ist es "SC"?
// if(getfroma6(???)) { PIN-Abfrage einschalten mit 0000; }
// ENABLE PIN HERE
d("I don't know how to enable the PIN.");
}
if(response = "+CPIN:SIM PIN") { // If the required password is PIN, continue.
sendtoa6("AT^CPINC=?");
if(getfroma6("^CPINC: 3")) { // If not exactly 3 attempts are left, abort. (should be larger than or equal to 3, shouldn't it?)
sendtoa6("AT+CPIN=\"0000\""); // Freischalten mit 0000
delay(50);
if(!getfroma6("OK")) { fail; } // If that was not the right password, abort.
sendtoa6("AT+CPWD=\"SC\",\"0000\",\"1996\"");// PIN ändern
delay(500);
if(getfroma6("OK")) { Serial.println("Looking good."); }
// sendtoa6("AT+CMGD=0,4"); // Should delete all SMS
// if(getfroma6(???)) { ; }
d("I don't know how to delete SMS.");
sendtoa6("AT+CPIN?");
if(getfroma6("+CPIN:READY")) {
// TURN OFF A& MODULE FOR SAFE CARD REMOVAL
digitalWrite(successled, HIGH);
}
} else { // If not exactly 3 times left
fail;
}
delay(1000);
}
}
}
void fail() {
digitalWrite(failled, HIGH); // Turn red fail LED on and ...
d("Something failed! Ending programme.");
while(1); // ... don't do anything anymore.
}
void wrongpin() {
digitalWrite(wrongpinled, HIGH); // Turn yellow LED on and ...
d("Wrong PIN! Ending programme.");
while(1); // ... don't do anything anymore.
}
//boolean getfroma6(char str[32], char str1[32], char str1[32], char str1[32], char str1[32], char str1[32]) { // Returns true if the passed (expected) message was received, false if anything else was received.
boolean getfroma6(char str[32]) { // Returns true if the passed (expected) message was received, false if anything else was received.
boolean asexpected = false;
for (i = 1; i < 9; ++i) {
receivelinefroma6();
// d("d1 "+response);
if(received[0] == '\0') { // If the received message is empty
continue;
} else {
response = received;
}
if(strstr(received, "ERROR") != NULL) { // If the received message contains "ERROR"
d("Received an error: "+response);
fail;
}
if(received[0] == '+') { // If the received message starts with a "+" sign
d("Reseived response: "+response);
} else {
if(strstr(received, "OK") != NULL) { // If the received message is "OK"
d("Received OK.");
asexpected = true; // Also treat OK like the expected message. No unexpected OK should ever be sent from the A6 module. So this is fine. No, it is, really.
// if(asexpected) { return true; }
} else {
if(strncmp(received,"AT",2) == 0) { // If the received message starts with "AT"
d("Received AT: "+response);
} else { // For any other received message
d("Received: "+response);
}
}
}
newData = false;
if(strstr(received, str) != NULL) {
d("Received expected message: "+response);
asexpected = true;
}
}
if(asexpected) { return true; }
return false;
}
//void handleresponse() {
// response = received;
// if(received[0] == '+') {
// d(" Response: "+response);
// } else {
// if(strstr(received, "OK") != NULL) {
// d(" It's okay.");
// } else {
// if(strncmp(received,"AT",2) == 0) {
// d(" I've sent: "+response);
// }
// }
// }
// newData = false;
//}
void receivelinefroma6() {
delay(80);
static byte counter = 0;
char rc;
received[0] = '\0';
while (A6MODULE.available() > 0 && newData == false) {
rc = A6MODULE.read();
if (rc != '\n') {
received[counter] = rc;
counter++;
if (counter >= maxmsglength) {
counter = maxmsglength - 1;
}
}
else {
received[counter] = '\0'; // terminate the string
counter = 0;
newData = true;
}
}
}
void sendtoa6(String command) {
// Serial.println(command);
A6MODULE.println(command);
d("Sent: "+command);
}
void d(String line) {
if (debug == true) { Serial.println(line); }
}
(tba:connections,assembly,photos?)