├── .gitattributes ├── .gitignore ├── 3D ├── DialForNewSentrySafe.stl ├── NautilusPulley.stl ├── Safe3Dpieces.jpg ├── SafeCrackerArduinoMount.stl ├── SafeCrackerDial3.stl └── SafeHandleMain.stl ├── Firmware └── SafeCracker │ ├── ControlFunctions.ino │ ├── SafeCracker.ino │ ├── cracking.ino │ ├── display.ino │ ├── measuring.ino │ ├── nvm.h │ └── testing.ino ├── Hardware ├── Safe Cracker Shield.brd ├── Safe Cracker Shield.pdf └── Safe Cracker Shield.sch ├── LICENSE.md ├── README.md └── live_crack_serial_log.log /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore list for Eagle, a PCB layout tool 2 | 3 | # Backup files 4 | *.s#? 5 | *.b#? 6 | *.l#? 7 | 8 | # Eagle project file 9 | # It contains a serial number and references to the file structure 10 | # on your computer. 11 | # comment the following line if you want to have your project file included. 12 | eagle.epf 13 | 14 | # Autorouter files 15 | *.pro 16 | *.job 17 | 18 | # CAM files 19 | *.$$$ 20 | *.cmp 21 | *.ly2 22 | *.l15 23 | *.sol 24 | *.plc 25 | *.stc 26 | *.sts 27 | *.crc 28 | *.crs 29 | 30 | *.dri 31 | *.drl 32 | *.gpi 33 | *.pls 34 | 35 | *.drd 36 | *.drd.* 37 | 38 | *.info 39 | 40 | *.eps 41 | 42 | # file locks introduced since 7.x 43 | *.lck 44 | 45 | -------------------------------------------------------------------------------- /3D/DialForNewSentrySafe.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfunX/Safe_Cracker/50a5bc5d43b29f653f6a87770265c4c0cdb0ea94/3D/DialForNewSentrySafe.stl -------------------------------------------------------------------------------- /3D/NautilusPulley.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfunX/Safe_Cracker/50a5bc5d43b29f653f6a87770265c4c0cdb0ea94/3D/NautilusPulley.stl -------------------------------------------------------------------------------- /3D/Safe3Dpieces.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfunX/Safe_Cracker/50a5bc5d43b29f653f6a87770265c4c0cdb0ea94/3D/Safe3Dpieces.jpg -------------------------------------------------------------------------------- /3D/SafeCrackerArduinoMount.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfunX/Safe_Cracker/50a5bc5d43b29f653f6a87770265c4c0cdb0ea94/3D/SafeCrackerArduinoMount.stl -------------------------------------------------------------------------------- /3D/SafeCrackerDial3.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfunX/Safe_Cracker/50a5bc5d43b29f653f6a87770265c4c0cdb0ea94/3D/SafeCrackerDial3.stl -------------------------------------------------------------------------------- /3D/SafeHandleMain.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfunX/Safe_Cracker/50a5bc5d43b29f653f6a87770265c4c0cdb0ea94/3D/SafeHandleMain.stl -------------------------------------------------------------------------------- /Firmware/SafeCracker/ControlFunctions.ino: -------------------------------------------------------------------------------- 1 | /* 2 | These are the general control functions 3 | 4 | setDial - go to a given dial position (0 to 99) 5 | resetDial - spin the dial twice to move reset discs A, B, and C. Stop at 0. 6 | findFlag - tell dial to return to where the flag interruts the photogate. Depending on how the robot is attached to dial, this may be true zero or an offset 7 | setDiscsToStart - Go home, clear dial, go to starting numbers 8 | 9 | Supporting functions: 10 | gotoStep - go to a certain step value out of 8400. The DC motor has momentum so this function, as best as possible, goes to a step value 11 | stepRequired - calcs the steps of the encoder to get from current position to a given position 12 | convertDialToEncoder - given a dial number, how many encoder steps is that? 13 | convertEncoderToDial - given encoder value, what is the dial position? 14 | enable/disableMotor - controls the enable pin on motor driver 15 | turnCW/CCW - controls the direction pin on motor driver 16 | 17 | */ 18 | 19 | //Given a step value, go to that step 20 | //Assume user has set direction prior to calling 21 | //Returns the delta from what you asked and where it ended up 22 | //Adding a full rotation will add a 360 degree full rotation 23 | int gotoStep(int stepGoal, boolean addAFullRotation) 24 | { 25 | //Coarse speed and window control how fast we arrive at the digit on the dial 26 | //Having too small of a window or too fast of an attack will make the dial 27 | //overshoot. 28 | int coarseSpeed = 200; //Speed at which we get to coarse window (0-255). 150, 200 works. 210, 230 fails 29 | int coarseWindow = 1250; //Once we are within this amount, switch to fine adjustment 30 | int fineSpeed = 50; //Less than 50 may not have enough torque 31 | int fineWindow = 32; //One we are within this amount, stop searching 32 | 33 | //Because we're switching directions we need to add extra steps to take 34 | //up the slack in the encoder 35 | if (direction == CW && previousDirection == CCW) 36 | { 37 | steps += switchDirectionAdjustment; 38 | if (steps > 8400) steps -= 8400; 39 | previousDirection = CW; 40 | } 41 | else if (direction == CCW && previousDirection == CW) 42 | { 43 | steps -= switchDirectionAdjustment; 44 | if (steps < 0) steps += 8400; 45 | previousDirection = CCW; 46 | } 47 | 48 | setMotorSpeed(coarseSpeed); //Go! 49 | while (stepsRequired(steps, stepGoal) > coarseWindow) ; //Spin until coarse window is closed 50 | 51 | //After we have gotten close to the first coarse window, proceed past the goal, then proceed to the goal 52 | if (addAFullRotation == true) 53 | { 54 | int tempStepGoal = steps + 8400/2; //Move 50 away from current position 55 | if (tempStepGoal > 8400) tempStepGoal -= 8400; 56 | 57 | //Go to temp position 58 | while (stepsRequired(steps, tempStepGoal) > coarseWindow) ; 59 | 60 | //Go to stepGoal 61 | while (stepsRequired(steps, stepGoal) > coarseWindow) ; //Spin until coarse window is closed 62 | } 63 | 64 | setMotorSpeed(fineSpeed); //Slowly approach 65 | 66 | while (stepsRequired(steps, stepGoal) > fineWindow) ; //Spin until fine window is closed 67 | 68 | setMotorSpeed(0); //Stop 69 | 70 | delay(timeMotorStop); //Wait for motor to stop 71 | 72 | int delta = steps - stepGoal; 73 | 74 | /*if (direction == CW) Serial.print("CW "); 75 | else Serial.print("CCW "); 76 | Serial.print("stepGoal: "); 77 | Serial.print(stepGoal); 78 | Serial.print(" / Final steps: "); 79 | Serial.print(steps); 80 | Serial.print(" / delta: "); 81 | Serial.print(delta); 82 | Serial.println();*/ 83 | 84 | return (delta); 85 | } 86 | 87 | //Calculate number of steps to get to our goal 88 | //Based on current position, goal, and direction 89 | //Corrects for 8400 roll over 90 | int stepsRequired(int currentSteps, int goal) 91 | { 92 | if (direction == CW) 93 | { 94 | if (currentSteps >= goal) return (currentSteps - goal); 95 | else if (currentSteps < goal) return (8400 - goal + currentSteps); 96 | } 97 | else if (direction == CCW) 98 | { 99 | if (currentSteps >= goal) return (8400 - currentSteps + goal); 100 | else if (currentSteps < goal) return (goal - currentSteps); 101 | } 102 | 103 | Serial.println(F("stepRequired failed")); //We shouldn't get this far 104 | Serial.print(F("Goal: ")); 105 | Serial.println(goal); 106 | Serial.print(F("currentSteps: ")); 107 | Serial.println(currentSteps); 108 | 109 | return (0); 110 | } 111 | 112 | //Given a dial number, goto that value 113 | //Assume user has set the direction before calling 114 | //Returns the dial value we actually ended on 115 | int setDial(int dialValue, boolean extraSpin) 116 | { 117 | //Serial.print("Want dialValue: "); 118 | //Serial.println(dialValue); 119 | 120 | int encoderValue = convertDialToEncoder(dialValue); //Get encoder value 121 | //Serial.print("Want encoderValue: "); 122 | //Serial.println(encoderValue); 123 | 124 | gotoStep(encoderValue, extraSpin); //Goto that encoder value 125 | //Serial.print("After movement, steps: "); 126 | //Serial.println(steps); 127 | 128 | int actualDialValue = convertEncoderToDial(steps); //Convert back to dial values 129 | //Serial.print("After movement, dialvalue: "); 130 | //Serial.println(actualDialValue); 131 | 132 | return (actualDialValue); 133 | } 134 | 135 | //Spin until we detect the photo gate trigger 136 | void findFlag() 137 | { 138 | byte fastSearch = 255; //Speed at which we locate photogate 139 | byte slowSearch = 50; 140 | 141 | turnCW(); 142 | 143 | //If the photogate is already detected spin until we are out 144 | if (flagDetected() == true) 145 | { 146 | Serial.println(F("We're too close to the photogate")); 147 | int currentDial = convertEncoderToDial(steps); 148 | currentDial += 50; 149 | if (currentDial > 100) currentDial -= 100; 150 | setDial(currentDial, false); //Advance to 50 dial ticks away from here 151 | } 152 | 153 | //Begin spinning 154 | setMotorSpeed(fastSearch); 155 | 156 | while (flagDetected() == false) delayMicroseconds(1); //Spin freely 157 | 158 | //Ok, we just zipped past the gate. Stop and spin slowly backward 159 | setMotorSpeed(0); 160 | delay(timeMotorStop); //Wait for motor to stop spinning 161 | turnCCW(); 162 | 163 | setMotorSpeed(slowSearch); 164 | while (flagDetected() == false) delayMicroseconds(1); //Find flag 165 | 166 | setMotorSpeed(0); 167 | delay(timeMotorStop); //Wait for motor to stop 168 | 169 | //Adjust steps with the real-world offset 170 | steps = (84 * homeOffset); //84 * the number the dial sits on when 'home' 171 | 172 | previousDirection = CCW; //Last adjustment to dial was in CCW direction 173 | } 174 | 175 | //Set the discs to the current combinations (user selectable) 176 | void resetDiscsWithCurrentCombo(boolean pause) 177 | { 178 | resetDial(); //Clear out everything 179 | 180 | //Set discs to this combo 181 | turnCCW(); 182 | int discAIsAt = setDial(discA, false); 183 | Serial.print("DiscA is at: "); 184 | Serial.println(discAIsAt); 185 | if (pause == true) messagePause("Verify disc position"); 186 | 187 | turnCW(); 188 | //Turn past disc B one extra spin 189 | int discBIsAt = setDial(discB, true); 190 | Serial.print("DiscB is at: "); 191 | Serial.println(discBIsAt); 192 | if (pause == true) messagePause("Verify disc position"); 193 | 194 | turnCCW(); 195 | int discCIsAt = setDial(discC, false); 196 | Serial.print("DiscC is at: "); 197 | Serial.println(discCIsAt); 198 | if (pause == true) messagePause("Verify disc position"); 199 | 200 | 201 | discCAttempts = -1; //Reset 202 | } 203 | 204 | 205 | //Given a dial value, covert to an encoder value (0 to 8400) 206 | //If there are 100 numbers on the dial, each number is 84 ticks wide 207 | int convertDialToEncoder(int dialValue) 208 | { 209 | int encoderValue = dialValue * 84; 210 | 211 | if (encoderValue > 8400) 212 | { 213 | Serial.print("Whoa! Your trying to go to a dial value that is illegal. encoderValue: "); 214 | Serial.println(encoderValue); 215 | while (1); 216 | } 217 | 218 | return (84 * dialValue); 219 | } 220 | 221 | //Given an encoder value, tell me where on the dial that equates 222 | //Returns 0 to 99 223 | //If there are 100 numbers on the dial, each number is 84 ticks wide 224 | int convertEncoderToDial(int encoderValue) 225 | { 226 | int dialValue = encoderValue / 84; //2388/84 = 28.43 227 | int partial = encoderValue % 84; //2388%84 = 36 228 | 229 | if (partial >= (84 / 2)) dialValue++; //36 < 42, so 28 is our final answer 230 | 231 | if (dialValue > 99) dialValue -= 100; 232 | 233 | return (dialValue); 234 | } 235 | 236 | //Reset the dial 237 | //Turn CCW, past zero, then continue until we return to zero 238 | void resetDial() 239 | { 240 | disableMotor(); 241 | 242 | turnCCW(); 243 | 244 | setMotorSpeed(255); //Go fast! 245 | enableMotor(); 246 | 247 | //Spin until 8400*2 steps have gone by 248 | int deltaSteps = 0; 249 | while (deltaSteps < (8400 * 2)) 250 | { 251 | int startingSteps = steps; //Remember where we started 252 | delay(100); //Let motor spin for awhile 253 | 254 | if (steps >= startingSteps) deltaSteps += steps - startingSteps; 255 | else deltaSteps += (8400 - startingSteps + steps); 256 | } 257 | 258 | setMotorSpeed(0); //Stop 259 | delay(timeMotorStop); //Alow motor to spin to a stop 260 | 261 | previousDirection = CCW; 262 | } 263 | 264 | //Tells the servo to pull down on the handle 265 | //After a certain amount of time we test to see the actual 266 | //position of the servo. If it's properly moved all the way down 267 | //then the handle has moved and door is open! 268 | //If position is not more than a determined amount, then return failure 269 | boolean tryHandle() 270 | { 271 | //Attempt to pull down on handle 272 | handleServo.write(servoTryPosition); 273 | delay(timeServoApply); //Wait for servo to move, but don't let it stall for too long and burn out 274 | 275 | //Check if we're there 276 | if (digitalRead(servoPositionButton) == LOW) 277 | { 278 | //Holy smokes we're there! 279 | return (true); 280 | } 281 | 282 | /* 283 | handlePosition = averageAnalogRead(servoPosition); 284 | //if (handlePosition > handleOpenPosition) //Used on old servo setup 285 | if (handlePosition < handleOpenPosition) 286 | { 287 | //Holy smokes we're there! 288 | return (true); 289 | } 290 | */ 291 | 292 | //Ok, we failed 293 | //Return to resting position 294 | handleServo.write(servoRestingPosition); 295 | delay(timeServoRelease); //Allow servo to release. 200 was too short on new safe 296 | 297 | return (false); 298 | } 299 | 300 | //Sets the motor speed 301 | //0 is no turn, 255 is max 302 | void setMotorSpeed(int speedValue) 303 | { 304 | analogWrite(motorPWM, speedValue); 305 | } 306 | 307 | //Gives the current being used by the motor in milliamps 308 | int readMotorCurrent() 309 | { 310 | const int VOLTAGE_REF = 5; //This board runs at 5V 311 | const float RS = 0.1; //Shunt resistor value (in ohms) 312 | 313 | float sensorValue = averageAnalogRead(currentSense); 314 | 315 | // Remap the ADC value into a voltage number (5V reference) 316 | sensorValue = (sensorValue * VOLTAGE_REF) / 1023; 317 | 318 | // Follow the equation given by the INA169 datasheet to 319 | // determine the current flowing through RS. Assume RL = 10k 320 | // Is = (Vout x 1k) / (RS x RL) 321 | float current = sensorValue / (10 * RS); 322 | 323 | return (current * 1000); 324 | } 325 | 326 | //Tell motor to turn dial clock wise 327 | void turnCW() 328 | { 329 | direction = CW; 330 | digitalWrite(motorDIR, HIGH); 331 | } 332 | 333 | //Tell motor to turn dial counter-clock wise 334 | void turnCCW() 335 | { 336 | direction = CCW; 337 | digitalWrite(motorDIR, LOW); 338 | } 339 | 340 | //Turn on the motor controller 341 | void enableMotor() 342 | { 343 | digitalWrite(motorReset, HIGH); 344 | } 345 | 346 | void disableMotor() 347 | { 348 | digitalWrite(motorReset, LOW); 349 | } 350 | 351 | //Interrupt routine when encoderA pin changes 352 | void countA() 353 | { 354 | if (direction == CW) steps--; 355 | else steps++; 356 | if (steps < 0) steps = 8399; //Limit variable to zero 357 | if (steps > 8399) steps = 0; //Limit variable to 8399 358 | } 359 | 360 | //Interrupt routine when encoderB pin changes 361 | void countB() 362 | { 363 | if (direction == CW) steps--; 364 | else steps++; 365 | if (steps < 0) steps = 8399; //Limit variable to zero 366 | if (steps > 8399) steps = 0; //Limit variable to 8399 367 | } 368 | 369 | //Checks to see if we detect the photogate being blocked by the flag 370 | boolean flagDetected() 371 | { 372 | if (digitalRead(photo) == LOW) return (true); 373 | return (false); 374 | } 375 | 376 | //Play a music tone to indicate door is open 377 | void announceSuccess() 378 | { 379 | //Beep! Piano keys to frequencies: http://www.sengpielaudio.com/KeyboardAndFrequencies.gif 380 | tone(buzzer, 2093, 150); //C 381 | delay(150); 382 | tone(buzzer, 2349, 150); //D 383 | delay(150); 384 | tone(buzzer, 2637, 150); //E 385 | delay(150); 386 | } 387 | 388 | //Disc C is the safety disc that prevents you from feeling the edges of the wheels 389 | //It has 12 upper and 12 low indents which means 100/24 = 4.16 per lower indent 390 | //So it moves a bit across the wheel. We could do floats, instead we'll do a lookup 391 | //Values were found by hand: What number is in the middle of the indent? 392 | //And later found by using the findIndents() function - which then stores values to EEPROM 393 | int lookupIndentValues(int indentNumber) 394 | { 395 | int indentCenterValue; 396 | EEPROM.get(LOCATION_INDENT_DIAL_0 + (indentNumber * sizeof(int)), indentCenterValue); //addr, variable to put it in 397 | return (convertEncoderToDial(indentCenterValue)); 398 | 399 | /* 400 | switch (indentNumber) 401 | { 402 | //Values found by measureIndents() function 403 | case 0: return (99); //98 to 1 on the wheel 404 | case 1: return (8); //6-9 405 | case 2: return (16); //14-17 406 | case 3: return (24); //23-26 407 | case 4: return (33); //31-34 408 | case 5: return (41); //39-42 409 | case 6: return (50); //48-51 410 | case 7: return (58); //56-59 411 | case 8: return (66); //64-67 412 | case 9: return (74); //73-76 413 | case 10: return (83); //81-84 414 | case 11: return (91); //90-93 415 | case 12: return (-1); //Not valid 416 | } 417 | */ 418 | } 419 | 420 | //Print a message and wait for user to press a button 421 | //Good for stepping through actions 422 | void messagePause(char* message) 423 | { 424 | Serial.println(message); 425 | while (!Serial.available()); //Wait for user input 426 | Serial.read(); //Throw away character 427 | } 428 | 429 | //See if user has pressed a button. If so, pause 430 | void checkForUserPause() 431 | { 432 | if (Serial.available()) //See if user has pressed a button 433 | { 434 | Serial.read(); //Throw out character 435 | Serial.print("Pausing. Press button to continue."); 436 | 437 | while (!Serial.available()); //Wait for user to press button to continue 438 | } 439 | } 440 | 441 | //Given a spot on the dial, what is the next available indent in the CCW direction 442 | //Takes care of wrap conditions 443 | //Returns the dial position of the next ident 444 | int getNextIndent(int currentDialPosition) 445 | { 446 | for (int x = 0 ; x < 12 ; x++) 447 | { 448 | if (indentsToTry[x] == true) //Are we allowed to use this indent? 449 | { 450 | byte nextDialPosition = lookupIndentValues(x); 451 | if (nextDialPosition > currentDialPosition) return (nextDialPosition); 452 | } 453 | } 454 | 455 | //If we never found a next dial value then we have wrap around situation 456 | //Find the first indent we can use 457 | for (int x = 0 ; x < 12 ; x++) 458 | { 459 | if (indentsToTry[x] == true) //Are we allowed to use this indent? 460 | { 461 | return (lookupIndentValues(x)); 462 | } 463 | } 464 | } 465 | 466 | //Takes an average of readings on a given pin 467 | //Returns the average 468 | int averageAnalogRead(byte pinToRead) 469 | { 470 | byte numberOfReadings = 8; 471 | unsigned int runningValue = 0; 472 | 473 | for (int x = 0 ; x < numberOfReadings ; x++) 474 | runningValue += analogRead(pinToRead); 475 | runningValue /= numberOfReadings; 476 | 477 | return (runningValue); 478 | } 479 | -------------------------------------------------------------------------------- /Firmware/SafeCracker/SafeCracker.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Opening a safe using a motor and a servo 3 | By: Nathan Seidle @ SparkFun Electronics 4 | Date: February 24th, 2017 5 | 6 | We use a motor with encoder feedback to try to glean the internal workings of the 7 | safe to reduce the overall solution domain. Then we begin running through 8 | solution domain, pulling on the handle and measuring it as we go. 9 | 10 | Motor current turning dial, speed = 255, ~350 to 560mA 11 | Motor current turning dial, speed = 50, = ~60 to 120mA 12 | 13 | TODO: 14 | servo rest, pull, and open settings to eeprom 15 | current combination to nvm 16 | */ 17 | 18 | #include "nvm.h" //EEPROM locations for settings 19 | #include //For storing settings and things to NVM 20 | 21 | #include 22 | Servo handleServo; 23 | 24 | //Pin definitions 25 | const byte encoderA = 2; 26 | const byte encoderB = 3; 27 | const byte photo = 5; 28 | const byte motorPWM = 6; 29 | const byte button = 7; 30 | const byte motorReset = 8; 31 | const byte servo = 9; 32 | const byte motorDIR = 10; 33 | const byte buzzer = 11; 34 | const byte LED = 13; 35 | 36 | const byte currentSense = A0; 37 | const byte servoPositionButton = A1; 38 | const byte displayLatch = A2; 39 | const byte displayClock = A3; 40 | const byte displayData = A4; 41 | 42 | //Servo values are found using the testServo() function while the 43 | //cracker was deattached from the safe 44 | 45 | //Default settings for 0.8 cubic ft. safe 46 | //On the 1st configuration increasing numbers cause handle to go down 47 | //const byte servoRestingPosition = 15; //Position not pulling/testing on handle 48 | //const byte servoPressurePosition = 50; //Position when doing indent measuring 49 | //const byte servoTryPosition = 80; //Position when testing handle 50 | 51 | //Default settings for 1.2 cubic ft. safe 52 | //On the 2nd larger configuration, decreasing numbers cause handle to go down 53 | int servoRestingPosition = 100; //Position not pulling/testing on handle 54 | int servoTryPosition = 50; //Position when testing handle 55 | int servoHighPressurePosition = 40; //Position when doing indent measuring 56 | 57 | const int timeServoApply = 350; //ms for servo to apply pressure. 350 works 58 | const int timeServoRelease = 250; //Allow servo to release. 250 works 59 | const int timeMotorStop = 125; //ms for motor to stop spinning after stop command. 200 works 60 | 61 | int handlePosition; //Used to see how far handle moved when pulled on 62 | //const int handleOpenPosition = 187; //Analog value. Must be less than analog value from servo testing. 63 | 64 | #define CCW 0 65 | #define CW 1 66 | 67 | volatile int steps = 0; //Keeps track of encoder counts. 8400 per revolution so this can get big. 68 | boolean direction = CW; //steps goes up or down based on direction 69 | boolean previousDirection = CW; //Detects when direction changes to add some steps for encoder slack 70 | byte homeOffset = 0; //Found by running findFlag(). Stored in nvm. 71 | 72 | //Because we're switching directions we need to add extra steps to take 73 | //up the slack in the encoder 74 | //The greater the adjustment the more negative it goes 75 | int switchDirectionAdjustment = (84 * 0) + 0; //Use 'Test dial control' to determine adjustment size 76 | //84 * 1 - 20 = Says 34 but is actually 33.5 (undershoot) 77 | //84 * 0 = Says 85 but is actually 85.9 (overshoot) 78 | 79 | //DiscA goes in CCW fashion during testing, increments 3 each new try. 80 | //We don't know if this is the center of the notch. If you exhaust the combination domain, adjust up or down one. 81 | #define DISCA_START 0 82 | 83 | //DiscB goes in CW fashion during testing, increments 3 each new try. 84 | #define DISCB_START (DISCA_START - 3) 85 | 86 | //DiscC goes in CCW fashion during testing. 12 indentations, 12 raised bits. Indents are 4.17 numbers wide. 87 | #define DISCC_START 0 88 | 89 | //These are the combination numbers we are attempting 90 | int discA = DISCA_START; 91 | int discB = DISCB_START; 92 | int discC = DISCC_START; 93 | 94 | //int discA = 30; //Testing only. Don't use. 95 | //int discB = 55; 96 | //int discC = 47; 97 | 98 | //Keeps track of the combos we need to try for each disc 99 | //byte maxAAttempts = 33; //Assume solution notch is 3 digits wide 100 | //byte maxBAttempts = 33; //Assume solution notch is 3 digits wide 101 | byte maxCAttempts = 0; //Calculated at startup 102 | 103 | //Keeps track of how many combos we've tried on a given disc 104 | //byte discAAttempts = 0; 105 | //byte discBAttempts = 0; 106 | byte discCAttempts = 0; 107 | 108 | long startTime; //Used to measure amount of time taken per test 109 | 110 | boolean buttonPressed = false; //Keeps track of the 'GO' button. 111 | 112 | boolean indentsToTry[12]; //Keeps track of the indents we want to try 113 | int indentLocations[12]; //Encoder click for a given indent 114 | int indentWidths[12]; //Calculated width of a given indent 115 | int indentDepths[12]; //Not really used 116 | 117 | void setup() 118 | { 119 | Serial.begin(115200); 120 | Serial.println(); 121 | Serial.println(); 122 | Serial.println("Safe Cracker"); 123 | 124 | pinMode(motorReset, OUTPUT); 125 | disableMotor(); 126 | 127 | pinMode(encoderA, INPUT); 128 | pinMode(encoderB, INPUT); 129 | 130 | pinMode(motorPWM, OUTPUT); 131 | pinMode(motorDIR, OUTPUT); 132 | 133 | pinMode(LED, OUTPUT); 134 | 135 | pinMode(currentSense, INPUT); 136 | pinMode(servoPositionButton, INPUT_PULLUP); 137 | 138 | pinMode(photo, INPUT_PULLUP); 139 | 140 | pinMode(button, INPUT_PULLUP); 141 | 142 | pinMode(displayClock, OUTPUT); 143 | pinMode(displayLatch, OUTPUT); 144 | pinMode(displayData, OUTPUT); 145 | 146 | digitalWrite(displayClock, LOW); 147 | digitalWrite(displayLatch, LOW); 148 | digitalWrite(displayData, LOW); 149 | 150 | //Setup the encoder interrupts. 151 | attachInterrupt(digitalPinToInterrupt(encoderA), countA, CHANGE); 152 | attachInterrupt(digitalPinToInterrupt(encoderB), countB, CHANGE); 153 | 154 | //Load settings from EEPROM 155 | homeOffset = EEPROM.read(LOCATION_HOME_OFFSET); //After doing a findFlag calibration, adjust this number up or down until dial is at zero 156 | Serial.print(F("Home Offset: ")); 157 | Serial.println(homeOffset); 158 | 159 | Serial.println(F("Indent data")); 160 | for (int indentNumber = 0 ; indentNumber < 12 ; indentNumber++) 161 | { 162 | //Load the yes/no for each indent To Try 163 | indentsToTry[indentNumber] = EEPROM.read(LOCATION_TEST_INDENT_0 + indentNumber); //Boolean 164 | 165 | //Get the encoder values for each indent 166 | EEPROM.get(LOCATION_INDENT_DIAL_0 + (indentNumber * 2), indentLocations[indentNumber]); //Addr, loc. Encoder click for a given indent 167 | 168 | Serial.print(F("IndentNum[")); 169 | Serial.print(indentNumber); 170 | Serial.print(F("] Encoder[")); 171 | Serial.print(indentLocations[indentNumber]); 172 | Serial.print(F("] / Dial[")); 173 | Serial.print(convertEncoderToDial(indentLocations[indentNumber])); 174 | Serial.print(F("] / Width[")); 175 | Serial.print(indentWidths[indentNumber]); 176 | Serial.print(F("] / Depth[")); 177 | Serial.print(indentDepths[indentNumber]); 178 | Serial.print(F("] Test[")); 179 | 180 | //Print Test if indent will be tested 181 | if (indentsToTry[indentNumber] == true) Serial.print("Y"); 182 | else Serial.print("N"); 183 | Serial.print(F("]")); 184 | 185 | Serial.println(); 186 | 187 | } 188 | 189 | //Get servo settings 190 | EEPROM.get(LOCATION_SERVO_REST, servoRestingPosition); 191 | EEPROM.get(LOCATION_SERVO_TEST_PRESSURE, servoTryPosition); 192 | EEPROM.get(LOCATION_SERVO_HIGH_PRESSURE, servoHighPressurePosition); 193 | 194 | //Validate settings 195 | if (servoRestingPosition > 250 || servoRestingPosition < 0) 196 | { 197 | //New board and/or EEPROM has not been set 198 | //Assign defaults 199 | servoRestingPosition = 100; 200 | servoTryPosition = 50; 201 | servoHighPressurePosition = 40; 202 | 203 | //Record these defaults to EEPROM 204 | EEPROM.put(LOCATION_SERVO_REST, servoRestingPosition); //addr, data 205 | EEPROM.put(LOCATION_SERVO_TEST_PRESSURE, servoTryPosition); 206 | EEPROM.put(LOCATION_SERVO_HIGH_PRESSURE, servoHighPressurePosition); 207 | } 208 | 209 | Serial.print(F("servo: resting[")); 210 | Serial.print(servoRestingPosition); 211 | Serial.print(F("] try[")); 212 | Serial.print(servoTryPosition); 213 | Serial.print(F("] HighPressure[")); 214 | Serial.print(servoHighPressurePosition); 215 | Serial.print(F("]")); 216 | Serial.println(); 217 | 218 | //Setup servo 219 | handleServo.attach(servo); 220 | handleServo.write(servoRestingPosition); //Goto the resting position (handle horizontal, door closed) 221 | delay(timeServoRelease * 3); //Allow servo to release, may be in solution slot 222 | 223 | randomSeed(analogRead(A5)); 224 | 225 | //Calculate how many indents we need to attempt on discC 226 | maxCAttempts = 0; 227 | for (byte x = 0 ; x < 12 ; x++) 228 | if (indentsToTry[x] == true) maxCAttempts++; 229 | 230 | //At startup discB may be negative. Fix it. 231 | if (discB < 0) discB += 100; 232 | 233 | //Tell dial to go to zero 234 | enableMotor(); //Turn on motor controller 235 | findFlag(); //Find the flag 236 | //Adjust steps with the real-world offset 237 | steps = (84 * homeOffset); //84 * the number the dial sits on when 'home' 238 | setDial(0, false); //Make dial go to zero 239 | 240 | clearDisplay(); 241 | showCombination(0, 0, 0); //Display zeroes 242 | } 243 | 244 | void loop() 245 | { 246 | char incoming; 247 | 248 | //Main serial control menu 249 | Serial.println(); 250 | Serial.print(F("Combo to start at: ")); 251 | Serial.print(discA); 252 | Serial.print("/"); 253 | Serial.print(discB); 254 | Serial.print("/"); 255 | Serial.print(discC); 256 | Serial.println(); 257 | 258 | Serial.println(F("1) Go home and reset dial")); 259 | Serial.println(F("2) Test dial control")); 260 | Serial.println(F("3) View indent positions")); 261 | Serial.println(F("4) Measure indents")); 262 | Serial.println(F("5) Set indents to test")); 263 | Serial.println(F("6) Set starting combos")); 264 | Serial.println(F("7) Calibrate handle servo")); 265 | Serial.println(F("8) Test handle button")); 266 | Serial.println(F("9) Test indent centers")); 267 | Serial.println(F("s) Start cracking")); 268 | 269 | while (!Serial.available()) 270 | { 271 | if (digitalRead(button) == LOW) 272 | { 273 | delay(50); 274 | if (digitalRead(button) == LOW) 275 | { 276 | buttonPressed = true; 277 | break; 278 | } 279 | else 280 | { 281 | buttonPressed = false; 282 | } 283 | } 284 | else 285 | { 286 | buttonPressed = false; 287 | } 288 | } 289 | 290 | if (buttonPressed == true) 291 | { 292 | Serial.println(F("Button pressed!")); 293 | 294 | while (digitalRead(button) == false); //Wait for user to stop pressing button 295 | buttonPressed = false; //Reset variable 296 | 297 | incoming = 's'; //Act as if user pressed start cracking 298 | } 299 | else 300 | { 301 | incoming = Serial.read(); 302 | } 303 | 304 | if (incoming == '1') 305 | { 306 | //Go to starting conditions 307 | findFlag(); //Detect the flag and center the dial 308 | 309 | Serial.print(F("Home offset is: ")); 310 | Serial.println(homeOffset); 311 | 312 | int zeroLocation = 0; 313 | while (1) //Loop until we have good input 314 | { 315 | Serial.print(F("Enter where dial is actually at: ")); 316 | 317 | while (!Serial.available()); //Wait for user input 318 | 319 | Serial.setTimeout(1000); //Must be long enough for user to enter second character 320 | zeroLocation = Serial.parseInt(); //Read user input 321 | 322 | Serial.print(zeroLocation); 323 | if (zeroLocation >= 0 && zeroLocation <= 99) break; 324 | Serial.println(F(" out of bounds")); 325 | } 326 | 327 | homeOffset = zeroLocation; 328 | 329 | Serial.print(F("\n\rSetting home offset to: ")); 330 | Serial.println(homeOffset); 331 | 332 | EEPROM.write(LOCATION_HOME_OFFSET, homeOffset); 333 | 334 | //Adjust steps with the real-world offset 335 | steps = (84 * homeOffset); //84 * the number the dial sits on when 'home' 336 | 337 | setDial(0, false); //Turn to zero 338 | 339 | Serial.println(F("Dial should be at: 0")); 340 | } 341 | else if (incoming == '2') 342 | { 343 | positionTesting(); //Pick random places on dial and prove we can go to them 344 | } 345 | else if (incoming == '3') 346 | { 347 | //View indent center values 348 | for (int x = 0 ; x < 12 ; x++) 349 | { 350 | Serial.print(x); 351 | Serial.print(F(": [")); 352 | Serial.print(lookupIndentValues(x)); 353 | Serial.print(F("]")); 354 | Serial.println(); 355 | } 356 | } 357 | else if (incoming == '4') 358 | { 359 | //Measure indents 360 | 361 | int measurements = 0; 362 | while (1) //Loop until we have good input 363 | { 364 | Serial.print(F("How many measurements would you like to take? (Start with 1)")); 365 | while (!Serial.available()); //Wait for user input 366 | Serial.setTimeout(1000); //Must be long enough for user to enter second character 367 | measurements = Serial.parseInt(); //Read user input 368 | 369 | if (measurements >= 1 && measurements <= 20) break; 370 | 371 | Serial.print(measurements); 372 | Serial.println(F(" out of bounds")); 373 | } 374 | Serial.println(measurements); 375 | 376 | measureDiscC(measurements); //Try to measure the idents in disc C. Give function the number of tests to run (get average). 377 | } 378 | else if (incoming == '5') 379 | { 380 | while (1) //Loop until exit 381 | { 382 | int indent = 0; 383 | while (1) //Loop until we have valid input 384 | { 385 | Serial.println("Indents to test:"); 386 | for (int x = 0 ; x < 12 ; x++) 387 | { 388 | Serial.print(x); 389 | Serial.print(": "); 390 | 391 | //Print Test if indent will be tested 392 | if (indentsToTry[x] == true) Serial.print("Y"); 393 | else Serial.print("N"); 394 | 395 | //Print dial value of this indent 396 | Serial.print(" / "); 397 | Serial.print(convertEncoderToDial(indentLocations[x])); 398 | 399 | Serial.println(); 400 | } 401 | Serial.println("Which indent to change?"); 402 | Serial.println("Type 99 to exit"); 403 | 404 | while (!Serial.available()); //Wait for user input 405 | 406 | Serial.setTimeout(500); //Must be long enough for user to enter second character 407 | indent = Serial.parseInt(); //Read user input 408 | 409 | if (indent >= 0 && indent <= 11) break; 410 | if (indent == 99) break; 411 | 412 | Serial.print(indent); 413 | Serial.println(F(" out of bounds")); 414 | } 415 | 416 | if (indent == 99) break; //User wants to exit 417 | 418 | //Flip it 419 | if (indentsToTry[indent] == true) indentsToTry[indent] = false; 420 | else indentsToTry[indent] = true; 421 | 422 | //Record current settings to EEPROM 423 | EEPROM.put(LOCATION_TEST_INDENT_0 + indent, indentsToTry[indent]); 424 | } 425 | 426 | //Calculate how many indents we need to attempt on discC 427 | maxCAttempts = 0; 428 | for (byte x = 0 ; x < 12 ; x++) 429 | if (indentsToTry[x] == true) maxCAttempts++; 430 | } 431 | else if (incoming == '6') 432 | { 433 | //Set starting combos 434 | for (byte x = 0 ; x < 3 ; x++) 435 | { 436 | int combo = 0; 437 | while (1) //Loop until we have good input 438 | { 439 | Serial.print(F("Enter Combination ")); 440 | if (x == 0) Serial.print("A"); 441 | else if (x == 1) Serial.print("B"); 442 | else if (x == 2) Serial.print("C"); 443 | Serial.print(F(" to start at: ")); 444 | while (!Serial.available()); //Wait for user input 445 | 446 | Serial.setTimeout(1000); //Must be long enough for user to enter second character 447 | combo = Serial.parseInt(); //Read user input 448 | 449 | if (combo >= 0 && combo <= 99) break; 450 | 451 | Serial.print(combo); 452 | Serial.println(F(" out of bounds")); 453 | } 454 | Serial.println(combo); 455 | if (x == 0) discA = combo; 456 | else if (x == 1) discB = combo; 457 | else if (x == 2) discC = combo; 458 | } 459 | 460 | } 461 | else if (incoming == '7') 462 | { 463 | testServo(); 464 | } 465 | else if (incoming == '8') 466 | { 467 | testHandleButton(); //Just pull down on handle and vary distance to hit open button 468 | } 469 | else if (incoming == '9') 470 | { 471 | 472 | setDial(0, false); //Turn to zero without extra spin 473 | 474 | //Test center point of each indent 475 | for (int indentNumber = 0 ; indentNumber < 12 ; indentNumber++) 476 | { 477 | 478 | //int dialValue = lookupIndentValues(indentNumber); //Get this indent's dial value 479 | //setDial(dialValue, false); //Go to the middle of this indent's location 480 | 481 | int encoderValue; 482 | EEPROM.get(LOCATION_INDENT_DIAL_0 + (indentNumber * 2), encoderValue); 483 | 484 | //84 allows bar to hit indent but just barely 485 | //* 2 lines the bar with indent nicely 486 | encoderValue += 84 * 2; 487 | 488 | gotoStep(encoderValue, false); //Goto that encoder value, no extra spin 489 | 490 | Serial.print("dialValue: "); 491 | Serial.print(convertEncoderToDial(encoderValue)); 492 | 493 | Serial.print(" encoderValue: "); 494 | Serial.println(encoderValue); 495 | 496 | handleServo.write(servoTryPosition); //Apply pressure to handle 497 | delay(timeServoApply); //Wait for servo to move 498 | handleServo.write(servoRestingPosition); //Release servo 499 | delay(timeServoRelease); //Allow servo to release 500 | } 501 | Serial.println(); 502 | 503 | } 504 | else if (incoming == 'a') 505 | { 506 | handleServo.write(servoRestingPosition); 507 | } 508 | else if (incoming == 'z') 509 | { 510 | handleServo.write(servoTryPosition); 511 | } 512 | else if (incoming == 's') //Start cracking! 513 | { 514 | clearDisplay(); 515 | showCombination(discA, discB, discC); //Update display 516 | 517 | startTime = millis(); 518 | 519 | //Set the discs to the current combinations (user can set if needed from menu) 520 | resetDiscsWithCurrentCombo(false); //Do not pause with messages 521 | 522 | while (1) 523 | { 524 | nextCombination(); //Try the next combo! 525 | 526 | if (Serial.available()) 527 | { 528 | byte incoming = Serial.read(); 529 | if (incoming == 'p') 530 | { 531 | Serial.println(F("Pausing")); 532 | while (!Serial.available()); 533 | Serial.read(); 534 | Serial.println(F("Running")); 535 | } 536 | else if (incoming == 'x' || incoming == 's') 537 | { 538 | Serial.println(F("Cracking stopped")); 539 | break; //User wants to stop 540 | } 541 | } 542 | } //End eombination loop 543 | } //End incoming == 's' 544 | } 545 | -------------------------------------------------------------------------------- /Firmware/SafeCracker/cracking.ino: -------------------------------------------------------------------------------- 1 | /* 2 | These are the actual high level cracking functions 3 | 4 | nextCombination() - Every time this is called, we attempt the current combo and then advance to next 5 | 6 | */ 7 | int combinationsAttempted = 0; 8 | 9 | //Given the current state of discs, advance to the next combo 10 | void nextCombination() 11 | { 12 | combinationsAttempted++; //Increase the overall count 13 | 14 | discCAttempts++; //There are as many as 12 indents to try. 15 | 16 | if (discCAttempts >= maxCAttempts) //Idents are exhausted, time to adjust discB 17 | { 18 | discCAttempts = 0; //Reset count 19 | 20 | //The interference width of B to A is 11, meaning, if discB is at 40 and discA 21 | //is at 30, if you move discB to 37 it will cause discA to move by 4 (to 26). 22 | boolean crossesA = checkCrossing(discB, -11, discA); //Check to see if the next B will cross A 23 | if (crossesA == true) //disc B is exhausted, time to adjust discA 24 | { 25 | discA += 3; //Disc A changes by 3 26 | if (discA > 99) 27 | { 28 | discA -= 100; 29 | Serial.println("Wrapping around zero."); 30 | //By moving every three and wrapping around zero the 31 | //safe will re-start but this time on a slightly different center (2 vs 0). 32 | //The cracker should find solution first time around but this should 33 | //increase the chance that it finds it on the 2nd search. 34 | } 35 | 36 | //Adjust discA, discB, discC 37 | discB = discA - 3; //Reset discB 38 | if (discB < 0) discB += 100; 39 | 40 | discC = getNextIndent(discB); //Get the first indent after B 41 | 42 | Serial.println("Resetting dial..."); 43 | 44 | findFlag(); //Re-home the dial between large finds 45 | 46 | //With this new A value, reset all discs 47 | resetDiscsWithCurrentCombo(false); 48 | discCAttempts = 0; //Reset count 49 | } 50 | else //Adjust discB and discC 51 | { 52 | //Serial.println("B: Adjust"); 53 | 54 | //Adjust discB to this new value 55 | turnCW(); 56 | 57 | discB -= 3; //Disc B changes by 3 58 | if (discB < 0) discB += 100; 59 | 60 | int discBIsAt = setDial(discB, false); 61 | //Serial.print("DiscB is at: "); 62 | //Serial.println(discBIsAt); 63 | //messagePause("Check dial position"); 64 | 65 | discC = getNextIndent(discB); //Get the first indent after B 66 | 67 | turnCCW(); 68 | 69 | //You can't have a combo that is X-45-46: too close. 70 | //There is a cross over point that comes when discB combo crosses 71 | //discC. When discC is within 3 of discB we must set discB then 72 | //immediately try discC (no move), then move to next discB. 73 | //This allows discC to continue correctly pushing discB around its 74 | //test set. 75 | if(abs(discB - discC) < 4) //C is too close to B 76 | { 77 | //Don't move C 78 | //Serial.println("Not moving C this time"); 79 | } 80 | else 81 | { 82 | //Move C 83 | int discCIsAt = setDial(discC, false); 84 | //Serial.print("DiscC is at: "); 85 | //Serial.println(discCIsAt); 86 | //messagePause("Check dial position"); 87 | } 88 | 89 | /*boolean crossesB = checkCrossing(discC, 3, discB); //Check to see if the next B will cross A 90 | if (crossesB == true) //We need to skip this test 91 | { 92 | //Do nothing 93 | //messagePause("skipping this discC"); 94 | } 95 | else 96 | { 97 | }*/ 98 | } 99 | } 100 | else //Adjust discC 101 | { 102 | //Serial.println("C: Adjust"); 103 | 104 | turnCCW(); 105 | 106 | discC = getNextIndent(discC); //Get next discC position 107 | 108 | //Adjust discC to this new value 109 | int discCIsAt = setDial(discC, false); 110 | //Serial.print("DiscC is at: "); 111 | //Serial.println(discCIsAt); 112 | } 113 | 114 | showCombination(discA, discB, discC); //Update display 115 | 116 | //Serial.print("Time, "); 117 | Serial.print(millis()); //Show timestamp 118 | 119 | Serial.print(", Combo, "); 120 | Serial.print(discA); 121 | Serial.print("/"); 122 | Serial.print(discB); 123 | Serial.print("/"); 124 | Serial.print(discC); 125 | 126 | //Try the handle 127 | if (tryHandle() == true) 128 | { 129 | Serial.print(", Handle position, "); 130 | Serial.print(handlePosition); 131 | 132 | Serial.println(); 133 | Serial.println("Door is open!!!"); 134 | announceSuccess(); 135 | disableMotor(); //Power down motor 136 | 137 | Serial.println(F("Pausing. Press key to release handle.")); 138 | while (!Serial.available()); 139 | Serial.read(); 140 | 141 | //Return to resting position 142 | handleServo.write(servoRestingPosition); 143 | delay(timeServoRelease); //Allow servo to release. 200 was too short on new safe 144 | 145 | while (1); //Freeze! 146 | } 147 | 148 | //Serial.print(", Handle position, "); 149 | //Serial.print(handlePosition); 150 | 151 | Serial.print(", attempt, "); 152 | Serial.print(combinationsAttempted); 153 | 154 | float secondsPerTest = (float)(millis() - startTime) / 1000.0 / combinationsAttempted; 155 | Serial.print(", seconds per attempt, "); 156 | Serial.print(secondsPerTest); 157 | 158 | Serial.println(); 159 | } 160 | 161 | //Given a disc position, and a change amount (negative is allowed) 162 | //Do we cross over the check spot? 163 | //The logic as it sits will have checkCrossing(10, -3, 7) return false, meaning 164 | //the cracker will try a combination with two same numbers (ie: 7/7/0) 165 | boolean checkCrossing(int currentSpot, int changeAmount, int checkSpot) 166 | { 167 | //Look at each step as we make a theoretical move from current to the check location 168 | for (int x = 0 ; x < abs(changeAmount) ; x++) 169 | { 170 | if (currentSpot == checkSpot) return (true); //If we make this move it will disrupt the disc with checkSpot 171 | 172 | if (changeAmount < 0) currentSpot--; 173 | else currentSpot++; 174 | 175 | if (currentSpot > 99) currentSpot = 0; 176 | if (currentSpot < 0) currentSpot = 99; 177 | } 178 | 179 | return (false); 180 | 181 | } 182 | 183 | -------------------------------------------------------------------------------- /Firmware/SafeCracker/display.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Functions to run the big seven segment display 3 | */ 4 | 5 | //Sends 6 ' ' characters and erases anything on the display 6 | void clearDisplay() 7 | { 8 | for (byte x = 0 ; x < 6 ; x++) 9 | { 10 | postNumber(' ', false, false); 11 | } 12 | 13 | //Latch the current segment data 14 | digitalWrite(displayLatch, LOW); 15 | digitalWrite(displayLatch, HIGH); //Register moves storage register on the rising edge of RCK 16 | } 17 | 18 | //Takes a value and displays 6 numbers. 19 | //Inverts every other number 20 | void showNumber(long number) 21 | { 22 | //Chop the value into the array 23 | byte values[6]; 24 | for (byte x = 0 ; x < 6 ; x++) 25 | { 26 | values[5 - x] = number % 10; 27 | number /= 10; 28 | } 29 | 30 | //Push the values out to the display 31 | for (byte x = 0 ; x < 6 ; x++) 32 | { 33 | if (x % 2 == 0) //Rotate number 34 | postNumber(values[x], false, true); //Don't write a decimal, rotate this digit 35 | else 36 | postNumber(values[x], false, false); //Don't write a decimal, not rotated 37 | } 38 | 39 | //Latch the current segment data - this causes display to show what's been clocked in 40 | digitalWrite(displayLatch, LOW); 41 | digitalWrite(displayLatch, HIGH); //Register moves storage register on the rising edge of RCK 42 | } 43 | 44 | //Given hours, minutes, seconds, display time with colons 45 | //Inverts every other number 46 | void showTime(long milliseconds) 47 | { 48 | long seconds = milliseconds / 1000; 49 | long minutes = seconds / 60; 50 | seconds %= 60; 51 | long hours = minutes / 60; 52 | minutes %= 60; 53 | 54 | //Chop the value into the array 55 | byte values[6]; 56 | values[0] = hours / 10; 57 | values[1] = hours % 10; 58 | values[2] = minutes / 10; 59 | values[3] = minutes % 10; 60 | values[4] = seconds / 10; 61 | values[5] = seconds % 10; 62 | 63 | //Push the values out to the display 64 | for (byte x = 0 ; x < 6 ; x++) 65 | { 66 | if (x % 2 == 0) //Rotate number 67 | { 68 | //Turn on decimal when x is 1 to 4 69 | if (x > 0 && x < 5) postNumber(values[x], true, true); //Write a decimal, rotate this digit 70 | else postNumber(values[x], false, true); //Don't write a decimal, rotate this digit 71 | } 72 | else 73 | { 74 | //Turn on decimal when x is 1 to 4 75 | if (x > 0 && x < 5) postNumber(values[x], true, false); //Write a decimal, not rotated 76 | else postNumber(values[x], false, false); //Don't write a decimal, not rotated 77 | } 78 | } 79 | 80 | //Latch the current segment data - this causes display to show what's been clocked in 81 | digitalWrite(displayLatch, LOW); 82 | digitalWrite(displayLatch, HIGH); //Register moves storage register on the rising edge of RCK 83 | } 84 | 85 | //Given the disc values, display them with separating colons 86 | //Inverts every other number 87 | void showCombination(byte discA, byte discB, byte discC) 88 | { 89 | //Chop the value into the array 90 | byte values[6]; 91 | values[0] = discA / 10; 92 | values[1] = discA % 10; 93 | values[2] = discB / 10; 94 | values[3] = discB % 10; 95 | values[4] = discC / 10; 96 | values[5] = discC % 10; 97 | 98 | //Push the values out to the display 99 | for (byte x = 0 ; x < 6 ; x++) 100 | { 101 | if (x % 2 == 0) //Rotate number 102 | { 103 | //Turn on decimal on 1 and 3 104 | if (x == 1 || x == 3) postNumber(values[x], true, true); //Write a decimal, rotate this digit 105 | else postNumber(values[x], false, true); //Don't write a decimal, rotate this digit 106 | } 107 | else 108 | { 109 | //Turn on decimal on 1 and 3 110 | if (x == 1 || x == 3) postNumber(values[x], true, false); //Write a decimal, not rotated 111 | else postNumber(values[x], false, false); //Don't write a decimal, not rotated 112 | } 113 | } 114 | 115 | //Latch the current segment data - this causes display to show what's been clocked in 116 | digitalWrite(displayLatch, LOW); 117 | digitalWrite(displayLatch, HIGH); //Register moves storage register on the rising edge of RCK 118 | } 119 | 120 | //Given a number, or '-', or ' ', shifts it out to the display 121 | //Inverting swaps all the segments around 122 | void postNumber(byte number, boolean decimal, boolean inverted) 123 | { 124 | // - A 125 | // / / F/B 126 | // - G 127 | // / / E/C 128 | // -. D/DP 129 | 130 | #define a 1<<0 131 | #define b 1<<6 132 | #define c 1<<5 133 | #define d 1<<4 134 | #define e 1<<3 135 | #define f 1<<1 136 | #define g 1<<2 137 | #define dp 1<<7 138 | 139 | byte segments; 140 | 141 | if (inverted == false) 142 | { 143 | switch (number) 144 | { 145 | case 1: segments = b | c; break; 146 | case 2: segments = a | b | d | e | g; break; 147 | case 3: segments = a | b | c | d | g; break; 148 | case 4: segments = f | g | b | c; break; 149 | case 5: segments = a | f | g | c | d; break; 150 | case 6: segments = a | f | g | e | c | d; break; 151 | case 7: segments = a | b | c; break; 152 | case 8: segments = a | b | c | d | e | f | g; break; 153 | case 9: segments = a | b | c | d | f | g; break; 154 | case 0: segments = a | b | c | d | e | f; break; 155 | case ' ': segments = 0; break; 156 | case '-': segments = g; break; 157 | } 158 | } 159 | else 160 | { 161 | switch (number) 162 | { 163 | // .- DP/D 164 | // / / C/E 165 | // - G 166 | // / / B/F 167 | // - A 168 | 169 | case 1: segments = e | f; break; 170 | case 2: segments = d | b | g | e | a; break; 171 | case 3: segments = d | f | g | e | a; break; 172 | case 4: segments = c | e | f | g; break; 173 | case 5: segments = d | c | g | f | a; break; 174 | case 6: segments = d | c | g | b | f | a; break; 175 | case 7: segments = d | e | f; break; 176 | case 8: segments = a | b | c | d | e | f | g; break; 177 | case 9: segments = a | c | d | e | f | g; break; 178 | case 0: segments = a | b | c | d | e | f; break; 179 | case ' ': segments = 0; break; 180 | case '-': segments = g; break; 181 | } 182 | 183 | } 184 | 185 | if (decimal) segments |= dp; 186 | 187 | //Clock these bits out to the drivers 188 | for (byte x = 0 ; x < 8 ; x++) 189 | { 190 | digitalWrite(displayClock, LOW); 191 | digitalWrite(displayData, segments & 1 << (7 - x)); 192 | digitalWrite(displayClock, HIGH); //Data transfers to the register on the rising edge of Clock 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /Firmware/SafeCracker/measuring.ino: -------------------------------------------------------------------------------- 1 | /* 2 | These functions are used to measure the internal indents on disc C 3 | */ 4 | 5 | 6 | //Locate and measure the 12 indents on disc C 7 | //We believe the width of the solution slot on disc C is smaller than the other 11 8 | //The depth of the indents is interesting but I worry the indent depth will vary 9 | //based on too many factors 10 | void measureDiscC(int numberOfTests) 11 | { 12 | //Clear arrays 13 | for (int x = 0 ; x < 12 ; x++) 14 | { 15 | indentLocations[x] = 0; 16 | indentWidths[x] = 0; 17 | indentDepths[x] = 0; 18 | } 19 | 20 | for (int testNumber = 0 ; testNumber < numberOfTests ; testNumber++) 21 | { 22 | for (int indentNumber = 0 ; indentNumber < 12 ; indentNumber++) 23 | { 24 | checkForUserPause(); //Pause if user presses a button 25 | 26 | findFlag(); //Recal every loop. Measuring causes havoc with the encoder 27 | 28 | //Look up where we should start looking 29 | int dialValue; 30 | if (indentNumber == 0) dialValue = 0; 31 | else dialValue = convertEncoderToDial(indentLocations[indentNumber - 1]) + 10; //Move to the middle of the high part following the last indent 32 | if (dialValue >= 100) dialValue -= 100; 33 | 34 | setDial(dialValue, false); //Go to the middle of this indent's location 35 | 36 | measureIndent(indentLocations[indentNumber], indentWidths[indentNumber], indentDepths[indentNumber]); 37 | 38 | Serial.print(F("Test ")); 39 | Serial.print(testNumber + 1); 40 | Serial.print(F(" of ")); 41 | Serial.print(numberOfTests); 42 | Serial.print(F(". IndentNum[")); 43 | Serial.print(indentNumber); 44 | Serial.print(F("] Encoder[")); 45 | Serial.print(indentLocations[indentNumber]); 46 | Serial.print(F("] / Dial[")); 47 | Serial.print(convertEncoderToDial(indentLocations[indentNumber])); 48 | Serial.print(F("] / Width[")); 49 | Serial.print(indentWidths[indentNumber]); 50 | Serial.print(F("] / Depth[")); 51 | Serial.print(indentDepths[indentNumber]); 52 | Serial.print("]"); 53 | Serial.println(); 54 | } 55 | } 56 | 57 | //Find the largest indent 58 | int biggestWidth = 0; 59 | int biggestIndent = 0; 60 | for (int x = 0 ; x < 12 ; x++) 61 | { 62 | if (indentWidths[x] > biggestWidth) 63 | { 64 | biggestWidth = indentWidths[x]; 65 | biggestIndent = x; 66 | } 67 | } 68 | Serial.print(F("Largest indent number: ")); 69 | Serial.println(biggestIndent); 70 | 71 | //Turn off all indents 72 | for (int x = 0 ; x < 12 ; x++) 73 | { 74 | indentsToTry[x] = false; //Clear local array 75 | EEPROM.put(LOCATION_TEST_INDENT_0 + x, false); //Record settings to EEPROM 76 | } 77 | 78 | //Turn on the one largest indent 79 | indentsToTry[biggestIndent] = true; 80 | EEPROM.put(LOCATION_TEST_INDENT_0 + biggestIndent, true); //Record settings to EEPROM 81 | Serial.println(F("Largest indent is now set to test")); 82 | 83 | //Record these indent values to EEPROM 84 | for (byte x = 0 ; x < 12 ; x++) 85 | EEPROM.put(LOCATION_INDENT_DIAL_0 + (x * 2), indentLocations[x]); //adr, data 86 | 87 | Serial.println(F("Indent locations recorded to EEPROM")); 88 | 89 | messagePause("Press key to continue"); 90 | } 91 | 92 | //From our current position, begin looking for and take internal measurement of the indents 93 | //on the blocking disc C 94 | //Caller has set the dial to wherever they want to start measuring from 95 | //Caller passes in two addresses where to record the data 96 | void measureIndent(int &indentLocation, int &indentWidth, int &indentDepth) 97 | { 98 | byte searchSlow = 50; //We don't want 255 fast, just a jaunt speed 99 | 100 | int edgeFar = 0; 101 | int edgeNear = 0; 102 | 103 | //Apply pressure to handle 104 | handleServo.write(servoHighPressurePosition); 105 | delay(timeServoApply); //Wait for servo to move 106 | 107 | //Spin until we hit the edge of the indent 108 | turnCW(); 109 | setMotorSpeed(searchSlow); //Begin turning dial 110 | 111 | long timeSinceLastMovement = millis(); 112 | int lastStep = steps; 113 | while (1) 114 | { 115 | if (lastStep != steps) 116 | { 117 | lastStep = steps; 118 | timeSinceLastMovement = millis(); 119 | } 120 | if (millis() - timeSinceLastMovement > 10) break; 121 | } 122 | setMotorSpeed(0); //Stop! 123 | 124 | delay(timeMotorStop); //Allow motor to stop spinning 125 | 126 | //Add offset because we're switching directions and we need to take up 127 | //slack in the encoder 128 | steps += switchDirectionAdjustment; 129 | if (steps > 8400) steps -= 8400; 130 | 131 | edgeFar = steps; //Take measurement 132 | 133 | 134 | //Ok, we've made it to the far edge of an indent. Now move backwards towards the other edge. 135 | turnCCW(); 136 | 137 | //Spin until we hit the opposite edge of the indent 138 | setMotorSpeed(searchSlow); //Begin turning dial 139 | 140 | timeSinceLastMovement = millis(); 141 | lastStep = steps; 142 | while (1) 143 | { 144 | if (lastStep != steps) 145 | { 146 | lastStep = steps; 147 | timeSinceLastMovement = millis(); 148 | } 149 | if (millis() - timeSinceLastMovement > 10) break; 150 | } 151 | setMotorSpeed(0); //Stop! 152 | 153 | delay(timeMotorStop); //Alow motor to stop spinning 154 | 155 | //Take a reading on how deep the handle got 156 | //handlePosition = averageAnalogRead(servoPosition); 157 | 158 | steps -= switchDirectionAdjustment; 159 | if (steps < 0) steps += 8400; 160 | 161 | edgeNear = steps; //Take measurement 162 | 163 | 164 | //Ok, we've made it to the near edge of an indent. Now move towards the other edge for a 2nd reading 165 | turnCW(); 166 | previousDirection = CW; //Last adjustment to dial was in CW direction 167 | 168 | //Now go back to original edge 169 | setMotorSpeed(searchSlow); //Begin turning dial 170 | 171 | timeSinceLastMovement = millis(); 172 | lastStep = steps; 173 | while (1) 174 | { 175 | if (lastStep != steps) 176 | { 177 | lastStep = steps; 178 | timeSinceLastMovement = millis(); 179 | } 180 | if (millis() - timeSinceLastMovement > 10) break; 181 | } 182 | setMotorSpeed(0); //Stop! 183 | 184 | delay(timeMotorStop); //Alow motor to stop spinning 185 | 186 | //Take a reading on how deep the handle got 187 | //handlePosition = averageAnalogRead(servoPosition); 188 | 189 | steps -= switchDirectionAdjustment; 190 | if (steps < 0) steps += 8400; 191 | 192 | int edgeFar2 = steps; //Take measurement 193 | 194 | 195 | 196 | //Measurement complete 197 | int sizeOfIndent; 198 | //if (edgeFar > edgeNear) sizeOfIndent = 8400 - edgeFar + edgeNear; 199 | //else sizeOfIndent = edgeNear - edgeFar; 200 | 201 | if (edgeFar2 > edgeNear) sizeOfIndent = 8400 - edgeFar2 + edgeNear; 202 | else sizeOfIndent = edgeNear - edgeFar2; 203 | 204 | //indentDepth += handlePosition; //Record handle depth 205 | indentWidth += sizeOfIndent; //Record this value to what the user passed us 206 | 207 | indentLocation = edgeFar2 + (sizeOfIndent / 2); //Find center of indent 208 | if (indentLocation >= 8400) indentLocation -= 8400; 209 | 210 | 211 | //Move away from edge of indent so we don't pinch the plunger 212 | turnCCW(); 213 | previousDirection = CCW; //Last adjustment to dial was in CW direction 214 | 215 | gotoStep(indentLocation, false); //Move to middle of this indent 216 | 217 | handleServo.write(servoRestingPosition); //Release servo 218 | delay(timeServoRelease * 2); //Give servo extra time to release because we are using extra pressure 219 | 220 | /* 221 | Serial.print("edgeNear: "); 222 | Serial.print(edgeNear); 223 | Serial.print(" / "); 224 | Serial.println(convertEncoderToDial(edgeNear)); 225 | Serial.print("edgeFar2: "); 226 | Serial.print(edgeFar2); 227 | Serial.print(" / "); 228 | Serial.println(convertEncoderToDial(edgeFar2)); 229 | 230 | //Display where the center of this indent is on the dial 231 | int centerOfIndent = sizeOfIndent / 2 + edgeFar; 232 | Serial.print("centerOfIndent: "); 233 | Serial.print(centerOfIndent); 234 | Serial.print(" / "); 235 | Serial.println(convertEncoderToDial(centerOfIndent)); 236 | */ 237 | } 238 | 239 | -------------------------------------------------------------------------------- /Firmware/SafeCracker/nvm.h: -------------------------------------------------------------------------------- 1 | #define LOCATION_HOME_OFFSET 0 2 | 3 | #define LOCATION_TEST_INDENT_0 1 4 | #define LOCATION_TEST_INDENT_1 2 5 | #define LOCATION_TEST_INDENT_2 3 6 | #define LOCATION_TEST_INDENT_3 4 7 | #define LOCATION_TEST_INDENT_4 5 8 | #define LOCATION_TEST_INDENT_5 6 9 | #define LOCATION_TEST_INDENT_6 7 10 | #define LOCATION_TEST_INDENT_7 8 11 | #define LOCATION_TEST_INDENT_8 9 12 | #define LOCATION_TEST_INDENT_9 10 13 | #define LOCATION_TEST_INDENT_10 11 14 | #define LOCATION_TEST_INDENT_11 12 15 | 16 | //Encoder values of each of the indents 17 | #define LOCATION_INDENT_DIAL_0 13 //Int storage 18 | #define LOCATION_INDENT_DIAL_1 15 19 | #define LOCATION_INDENT_DIAL_2 17 20 | #define LOCATION_INDENT_DIAL_3 19 21 | #define LOCATION_INDENT_DIAL_4 21 22 | #define LOCATION_INDENT_DIAL_5 23 23 | #define LOCATION_INDENT_DIAL_6 25 24 | #define LOCATION_INDENT_DIAL_7 27 25 | #define LOCATION_INDENT_DIAL_8 29 26 | #define LOCATION_INDENT_DIAL_9 31 27 | #define LOCATION_INDENT_DIAL_10 33 28 | #define LOCATION_INDENT_DIAL_11 35 29 | 30 | #define LOCATION_SERVO_REST 37 //Ints 31 | #define LOCATION_SERVO_TEST_PRESSURE 39 32 | #define LOCATION_SERVO_HIGH_PRESSURE 41 33 | 34 | 35 | -------------------------------------------------------------------------------- /Firmware/SafeCracker/testing.ino: -------------------------------------------------------------------------------- 1 | /* 2 | These functions are used to calibrate the dialer and handle servo 3 | */ 4 | 5 | //Spins the motor while we tweak the servo up/down to detect binding force and servo position 6 | //Press x to exit 7 | //How to use: Attach cracker to safe. 8 | //As motor spins increase handle pressure using a/z until the gear 9 | //just begins to rub 10 | //Move 10 away and then exit. This will be stored as servoRestPosition 11 | //servoTryPosition is stored as servoRestPosition - 50 12 | //servoPressurePosition is stored as servoRestPosition - 70 13 | void testServo() 14 | { 15 | Serial.println(F("x to exit")); 16 | Serial.println(F("a to adjust servo to gear")); 17 | Serial.println(F("z to adjust servo away from gear")); 18 | 19 | int servo = servoRestingPosition; 20 | handleServo.write(servo); 21 | 22 | long timeSinceLastMovement = millis(); 23 | int lastStep = steps; 24 | 25 | turnCW(); 26 | setMotorSpeed(50); 27 | 28 | enableMotor(); //Turn on motor controller 29 | 30 | while (1) 31 | { 32 | if (lastStep != steps) 33 | { 34 | lastStep = steps; 35 | timeSinceLastMovement = millis(); 36 | } 37 | if (millis() - timeSinceLastMovement > 25) 38 | { 39 | setMotorSpeed(0); //Stop! 40 | Serial.println("Dial stuck"); 41 | while (1); 42 | } 43 | 44 | if (Serial.available()) 45 | { 46 | byte incoming = Serial.read(); 47 | 48 | if (incoming == 'a') servo++; 49 | if (incoming == 'z') servo--; 50 | if (incoming == 'x') //Exit 51 | { 52 | setMotorSpeed(0); //Stop! 53 | handleServo.write(servoRestingPosition); //Goto the resting position (handle horizontal, door closed) 54 | delay(timeServoRelease); //Allow servo to move 55 | break; 56 | } 57 | 58 | if (servo < 0) servo = 0; 59 | if (servo > 255) servo = 255; 60 | 61 | handleServo.write(servo); //Goto the resting position (handle horizontal, door closed) 62 | } 63 | //int handlePosition = averageAnalogRead(servoPosition); //Old way 64 | int handlePosition = digitalRead(servoPositionButton); //Look for button being pressed 65 | 66 | Serial.print(F("servo: ")); 67 | Serial.print(servo); 68 | Serial.print(F(" / handleButton: ")); 69 | Serial.print(servoPositionButton); 70 | Serial.println(); 71 | 72 | delay(100); 73 | } 74 | 75 | //Record settings to EEPROM 76 | servoRestingPosition = servo + 10; //We want some clearance 77 | servoTryPosition = servoRestingPosition - 60; 78 | servoHighPressurePosition = servoRestingPosition - 60; 79 | 80 | if (servoHighPressurePosition < 0 || servoTryPosition < 0) 81 | { 82 | Serial.println(F("servoPressurePosition or servoTryPosition is negative. Adjust servo higher.")); 83 | Serial.println(F("Freezing")); 84 | while (1); //Freeze 85 | } 86 | 87 | EEPROM.put(LOCATION_SERVO_REST, servoRestingPosition); 88 | EEPROM.put(LOCATION_SERVO_TEST_PRESSURE, servoTryPosition); 89 | EEPROM.put(LOCATION_SERVO_HIGH_PRESSURE, servoHighPressurePosition); 90 | 91 | Serial.print(F("servo: resting[")); 92 | Serial.print(servoRestingPosition); 93 | Serial.print(F("] try[")); 94 | Serial.print(servoTryPosition); 95 | Serial.print(F("] Highpressure[")); 96 | Serial.print(servoHighPressurePosition); 97 | Serial.print(F("]")); 98 | Serial.println(); 99 | 100 | Serial.println(F("Servo positions stored.")); 101 | 102 | } 103 | 104 | //Pulls down on handle using current settings 105 | //Reports if button has been pressed 106 | //To use: before running function identify the solution slot 107 | //Dial will turn to slot 108 | //Press any letter to cause servo to pull on handle 109 | //If button is pressed, it will print Pressed!! 110 | //Adjust button down just below level of being pressed when handle is normally pulled on 111 | //Press x to exit 112 | void testHandleButton(void) 113 | { 114 | //Find the indent to test 115 | int solutionDiscC = 0; 116 | for (int x = 0 ; x < 12 ; x++) 117 | { 118 | if (indentsToTry[x] == true) 119 | solutionDiscC = convertEncoderToDial(indentLocations[x]); 120 | } 121 | setDial(solutionDiscC, false); //Goto this dial location 122 | 123 | Serial.println("x to exit"); 124 | Serial.println("a to pull on handle"); 125 | Serial.println("z to release handle"); 126 | 127 | int pressedCounter = 0; 128 | while (1) 129 | { 130 | if (Serial.available()) 131 | { 132 | byte incoming = Serial.read(); 133 | if (incoming == 'x') 134 | { 135 | //Release handle 136 | handleServo.write(servoRestingPosition); //Goto the resting position (handle horizontal, door closed) 137 | delay(timeServoRelease); //Allow servo to release 138 | 139 | return; //Exit 140 | } 141 | else if (incoming == 'a') 142 | { 143 | //Pull on handle 144 | handleServo.write(servoTryPosition); 145 | delay(timeServoApply); //Allow servo to move 146 | } 147 | else if (incoming == 'z') 148 | { 149 | //Release handle 150 | handleServo.write(servoRestingPosition); //Goto the resting position (handle horizontal, door closed) 151 | delay(timeServoRelease); //Allow servo to release 152 | } 153 | } 154 | 155 | if (digitalRead(servoPositionButton) == LOW) 156 | { 157 | pressedCounter++; 158 | Serial.print("Pressed! "); 159 | Serial.println(pressedCounter); //To have something that changes 160 | delay(100); 161 | } 162 | 163 | //Hang out for 100ms but scan button during that time 164 | for (byte x = 0 ; x < 100 ; x++) 165 | { 166 | if (digitalRead(servoPositionButton) == LOW) break; 167 | delay(1); 168 | } 169 | } 170 | 171 | } 172 | 173 | //Test to see if we can repeatably go to a dial position 174 | //Turns dial to random CW and CCW position and asks user to verify. 175 | //How to use: Attach cracker to safe. Home the dial using the menu function. Then run this 176 | //and verify the dial goes where it says it is going. If it's wrong, check homeOffset variable. 177 | //If one direction is off, check switchDirectionAdjustment variable. 178 | void positionTesting() 179 | { 180 | int randomDial; 181 | 182 | for (int x = 0 ; x < 2 ; x++) 183 | { 184 | randomDial = random(0, 100); 185 | randomDial = 25; 186 | turnCCW(); 187 | setDial(randomDial, false); 188 | 189 | Serial.print(F("Dial should be at: ")); 190 | Serial.println(convertEncoderToDial(steps)); 191 | messagePause("Verify then press key to continue"); 192 | 193 | randomDial = random(0, 100); 194 | randomDial = 75; 195 | turnCW(); 196 | setDial(randomDial, false); 197 | 198 | Serial.print(F("Dial should be at: ")); 199 | Serial.println(convertEncoderToDial(steps)); 200 | messagePause("Verify then press key to exit"); 201 | } 202 | } 203 | 204 | -------------------------------------------------------------------------------- /Hardware/Safe Cracker Shield.brd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | B 161 | A 162 | ENC 163 | MTR 164 | Safe Cracker 165 | Power 166 | 167 | 168 | 169 | 170 | 171 | X10 172 | RED 173 | BRN 174 | Motor 175 | Control 176 | Current 177 | Sense 178 | Motor 179 | Photo 180 | Handle 181 | WHT 182 | WHT 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | N. Seidle 192 | Btn 193 | Display 194 | RED 195 | BLK 196 | 197 | 198 | 199 | <h3>SparkFun Electronics' preferred foot prints</h3> 200 | In this library you'll find non-functional items- supply symbols, logos, notations, frame blocks, etc.<br><br> 201 | We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. 202 | <br><br> 203 | <b>Licensing:</b> CC v3.0 Share-Alike You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | <h3>Creative Commons License Template</h3> 244 | <p>CC BY-SA 4.0 License with <a href="https://creativecommons.org/licenses/by-sa/4.0/">link to license</a> and placeholder for designer name.</p> 245 | <p>Devices using: 246 | <ul><li>FRAME_LEDGER</li> 247 | <li>FRAME_LETTER</li></ul></p> 248 | Released under the Creative Commons Attribution Share-Alike 4.0 License 249 | https://creativecommons.org/licenses/by-sa/4.0/ 250 | Designed by: 251 | 252 | 253 | 254 | 255 | <h3>SparkFun Electronics' preferred foot prints</h3> 256 | In this library you'll find connectors and sockets- basically anything that can be plugged into or onto.<br><br> 257 | We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. 258 | <br><br> 259 | <b>Licensing:</b> CC v3.0 Share-Alike You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | >NAME 307 | >VALUE 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | INSERT 336 | 337 | 338 | 339 | 340 | 341 | 342 | <h3>Plated Through Hole - 5 Pin</h3> 343 | <p>Specifications: 344 | <ul><li>Pin count:5</li> 345 | <li>Pin pitch:0.1"</li> 346 | </ul></p> 347 | <p>Example device(s): 348 | <ul><li>CONN_05</li> 349 | </ul></p> 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | >NAME 393 | >VALUE 394 | 395 | 396 | <h3>Plated Through Hole -8 Pin</h3> 397 | <p>Specifications: 398 | <ul><li>Pin count:8</li> 399 | <li>Pin pitch:0.1"</li> 400 | </ul></p> 401 | <p>Example device(s): 402 | <ul><li>CONN_08</li> 403 | </ul></p> 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | >NAME 471 | >VALUE 472 | 473 | 474 | <h3>Plated Through Hole - 4 Pin</h3> 475 | <p>Specifications: 476 | <ul><li>Pin count:4</li> 477 | <li>Pin pitch:0.1"</li> 478 | </ul></p> 479 | <p>Example device(s): 480 | <ul><li>CONN_04</li> 481 | </ul></p> 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | >NAME 517 | >VALUE 518 | 519 | 520 | <h3>JST 3 Pin Vertical Plated Through Hole</h3> 521 | <p>Specifications: 522 | <ul><li>Pin count:3</li> 523 | <li>Pin pitch:2mm</li> 524 | </ul></p> 525 | <p><a href=”https://www.sparkfun.com/datasheets/Prototyping/ePH.pdf”>Datasheet referenced for footprint</a></p> 526 | <p>Example device(s): 527 | <ul><li>CONN_03</li> 528 | </ul></p> 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | + 541 | - 542 | S 543 | >NAME 544 | >VALUE 545 | 546 | 547 | <h3>Plated Through Hole - 6 Pin</h3> 548 | <p>Specifications: 549 | <ul><li>Pin count:6</li> 550 | <li>Pin pitch:0.1"</li> 551 | </ul></p> 552 | <p>Example device(s): 553 | <ul><li>CONN_06</li> 554 | </ul></p> 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | >NAME 606 | >VALUE 607 | 608 | 609 | <h3>JST -XH Male 2.5mm Vertical Plated-Through-Hole</h3> 610 | <p>Specifications: 611 | <ul><li>Pin count: 2</li> 612 | <li>Pin pitch: 2.5mm</li> 613 | </ul></p> 614 | <p><a href=”http://www.jst-mfg.com/product/pdf/eng/eXH.pdf”>Datasheet referenced for footprint</a></p> 615 | <p>Example device(s): 616 | <ul><li>CONN_02</li> 617 | </ul></p> 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | >Name 629 | >Value 630 | + 631 | - 632 | 633 | 634 | 635 | 636 | <h3>SparkFun Electronics' preferred foot prints</h3> 637 | In this library you'll find boards and modules: Arduino footprints, breadboards, non-RF modules, etc.<br><br> 638 | We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. 639 | <br><br> 640 | <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ 641 | <br><br> 642 | You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | RST 720 | ISP 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | 955 | 956 | 957 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | <h3>SparkFun Electromechanical Parts</h3> 986 | This library contains electromechanical devices, like motors, speakers,servos, and relays. 987 | <br> 988 | <br> 989 | We've spent an enormous amount of time creating and checking these footprints and parts, but it is <b> the end user's responsibility</b> to ensure correctness and suitablity for a given componet or application. 990 | <br> 991 | <br>If you enjoy using this library, please buy one of our products at <a href=" www.sparkfun.com">SparkFun.com</a>. 992 | <br> 993 | <br> 994 | <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ 995 | <br> 996 | <br> 997 | You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. 998 | 999 | 1000 | <h3>12mm Buzzer - PTH</h3> 1001 | <p>This is a small 12mm round speaker that operates around the audible 2kHz range. You can use these speakers to create simple music or user interfaces.</p> 1002 | <p><a href="http://cdn.sparkfun.com/datasheets/Components/General/cem-1203-42-.pdf">Datasheet</a></p> 1003 | <h4>Devices Using</h4> 1004 | <ul><li>BUZZER</li></ul> 1005 | 1006 | 1007 | 1008 | 1009 | >NAME 1010 | >VALUE 1011 | 1012 | 1013 | 1014 | 1015 | <h3>SparkFun LEDs</h3> 1016 | This library contains discrete LEDs for illumination or indication, but no displays. 1017 | <br> 1018 | <br> 1019 | We've spent an enormous amount of time creating and checking these footprints and parts, but it is <b> the end user's responsibility</b> to ensure correctness and suitablity for a given componet or application. 1020 | <br> 1021 | <br>If you enjoy using this library, please buy one of our products at <a href=" www.sparkfun.com">SparkFun.com</a>. 1022 | <br> 1023 | <br> 1024 | <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ 1025 | <br> 1026 | <br> 1027 | You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. 1028 | 1029 | 1030 | <B>LED 5mm PTH</B><p> 1031 | 5 mm, round 1032 | <p>Specifications: 1033 | <ul><li>Pin count: 2</li> 1034 | <li>Pin pitch: 0.1inch</li> 1035 | <li>Diameter: 5mm</li> 1036 | </ul></p> 1037 | <p>Example device(s): 1038 | <ul><li>LED-IR-THRU</li> 1039 | 1040 | 1041 | 1042 | 1043 | 1044 | 1045 | 1046 | 1047 | 1048 | >NAME 1049 | >VALUE 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | 1064 | 1065 | 1066 | 1067 | 1068 | 1069 | <b>SparkFun 2 Layer Design Rule Checks - STANDARD/TIGHT/FAB-LIMIT</b> 1070 | <p> 1071 | These rules have been curated by SparkFuns DFM commitee. After doing much research, communicating with our multiple fab houses, and getting quotes of various designs, we have compiled three DRU files. 1072 | <p> 1073 | <b>STANDARD:</b> This is more of a "best case scenario" set of limitations. If your design has the space, and/or you have the time to work within these parameters, please do. Larger trace width and clearance makes for easier visual inspection of the PCB while troubleshooting (useful in production and to the end user). It also allows for better ability to hack a trace (if you are crazy enough to scrape away the mask and solder to a trace). Another thing to keep in mind is that more metal is just more robust. 1074 | <p> 1075 | <b>TIGHT:</b> This is where cost comes into play. We have found that most fab houses begin to add extra charges when you go smaller than these specs. In some cases, going to less than 15 mil trace can increase the cost by 10%. (This is why we have set the min drill on this DRU to 15 mil) Same story for traces thinner than 7 mil. To avoid those extra charges, then stay within the rules of this DRU. 1076 | <p> 1077 | <b>FAB-LIMIT:</b> These set of rules are at the very limit of most fab houses capabilities. You will pay more for these specs, and it should be used on designs that have a darned good reason to need 4 mil vias and 4 mil traces. 1078 | <p> 1079 | **NOTE Clearance, Distance, Sizes, and Restring are all set to different limits in each of these three DRU files. Please compare the files within the CAM job editor window of eagle to see all the numbers. 1080 | <p> 1081 | ***NOTE, Please set your Net Classes to default (0mil for all settings), so that it won't effect the DRC when you run it with these settings. 1082 | 1083 | 1084 | 1085 | 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | 1106 | 1107 | 1108 | 1109 | 1110 | 1111 | 1112 | 1113 | 1114 | 1115 | 1116 | 1117 | 1118 | 1119 | 1120 | 1121 | 1122 | 1123 | 1124 | 1125 | 1126 | 1127 | 1128 | 1129 | 1130 | 1131 | 1132 | 1133 | 1134 | 1135 | 1136 | 1137 | 1138 | 1139 | 1140 | 1141 | 1142 | 1143 | 1144 | 1145 | 1146 | 1147 | 1148 | 1149 | 1150 | 1151 | 1152 | 1153 | 1154 | 1155 | 1156 | 1157 | 1158 | 1159 | 1160 | 1161 | 1162 | 1163 | 1164 | 1165 | 1166 | 1167 | 1168 | 1169 | 1170 | 1171 | 1172 | 1173 | 1174 | 1175 | 1176 | 1177 | 1178 | 1179 | 1180 | 1181 | 1182 | 1183 | 1184 | 1185 | 1186 | 1187 | 1188 | 1189 | 1190 | 1191 | 1192 | 1193 | 1194 | 1195 | 1196 | 1197 | 1198 | 1199 | 1200 | 1201 | 1202 | 1203 | 1204 | 1205 | 1206 | 1207 | 1208 | 1209 | 1210 | 1211 | 1212 | 1213 | 1214 | 1215 | 1216 | 1217 | 1218 | 1219 | 1220 | 1221 | 1222 | 1223 | 1224 | 1225 | 1226 | 1227 | 1228 | 1229 | 1230 | 1231 | 1232 | 1233 | 1234 | 1235 | 1236 | 1237 | 1238 | 1239 | 1240 | 1241 | 1242 | 1243 | 1244 | 1245 | 1246 | 1247 | 1248 | 1249 | 1250 | 1251 | 1252 | 1253 | 1254 | 1255 | 1256 | 1257 | 1258 | 1259 | 1260 | 1261 | 1262 | 1263 | 1264 | 1265 | 1266 | 1267 | 1268 | 1269 | 1270 | 1271 | 1272 | 1273 | 1274 | 1275 | 1276 | 1277 | 1278 | 1279 | 1280 | 1281 | 1282 | 1283 | 1284 | 1285 | 1286 | 1287 | 1288 | 1289 | 1290 | 1291 | 1292 | 1293 | 1294 | 1295 | 1296 | 1297 | 1298 | 1299 | 1300 | 1301 | 1302 | 1303 | 1304 | 1305 | 1306 | 1307 | 1308 | 1309 | 1310 | 1311 | 1312 | 1313 | 1314 | 1315 | 1316 | 1317 | 1318 | 1319 | 1320 | 1321 | 1322 | 1323 | 1324 | 1325 | 1326 | 1327 | 1328 | 1329 | 1330 | 1331 | 1332 | 1333 | 1334 | 1335 | 1336 | 1337 | 1338 | 1339 | 1340 | 1341 | 1342 | 1343 | 1344 | 1345 | 1346 | 1347 | 1348 | 1349 | 1350 | 1351 | 1352 | 1353 | 1354 | 1355 | 1356 | 1357 | 1358 | 1359 | 1360 | 1361 | 1362 | 1363 | 1364 | 1365 | 1366 | 1367 | 1368 | 1369 | 1370 | 1371 | 1372 | 1373 | 1374 | 1375 | 1376 | 1377 | 1378 | 1379 | 1380 | 1381 | 1382 | 1383 | 1384 | 1385 | 1386 | 1387 | 1388 | 1389 | 1390 | 1391 | 1392 | 1393 | 1394 | 1395 | 1396 | 1397 | 1398 | 1399 | 1400 | 1401 | 1402 | 1403 | 1404 | 1405 | 1406 | 1407 | 1408 | 1409 | 1410 | 1411 | 1412 | 1413 | 1414 | 1415 | 1416 | 1417 | 1418 | 1419 | 1420 | 1421 | 1422 | 1423 | 1424 | 1425 | 1426 | 1427 | 1428 | 1429 | 1430 | 1431 | 1432 | 1433 | 1434 | 1435 | 1436 | 1437 | 1438 | 1439 | 1440 | 1441 | 1442 | 1443 | 1444 | 1445 | 1446 | 1447 | 1448 | 1449 | 1450 | 1451 | 1452 | 1453 | 1454 | 1455 | 1456 | 1457 | 1458 | 1459 | 1460 | 1461 | 1462 | 1463 | 1464 | 1465 | 1466 | 1467 | 1468 | 1469 | 1470 | 1471 | 1472 | 1473 | 1474 | 1475 | 1476 | 1477 | 1478 | 1479 | 1480 | 1481 | 1482 | 1483 | 1484 | 1485 | 1486 | 1487 | 1488 | 1489 | 1490 | 1491 | 1492 | 1493 | 1494 | 1495 | 1496 | 1497 | 1498 | 1499 | 1500 | 1501 | 1502 | 1503 | 1504 | 1505 | 1506 | 1507 | 1508 | 1509 | 1510 | 1511 | 1512 | 1513 | 1514 | 1515 | 1516 | 1517 | 1518 | 1519 | 1520 | 1521 | 1522 | 1523 | 1524 | 1525 | 1526 | 1527 | 1528 | 1529 | 1530 | 1531 | 1532 | 1533 | 1534 | 1535 | 1536 | 1537 | 1538 | 1539 | 1540 | 1541 | 1542 | 1543 | 1544 | 1545 | 1546 | 1547 | 1548 | 1549 | 1550 | 1551 | 1552 | 1553 | 1554 | 1555 | 1556 | 1557 | 1558 | 1559 | 1560 | 1561 | 1562 | 1563 | 1564 | 1565 | 1566 | 1567 | 1568 | 1569 | 1570 | 1571 | 1572 | 1573 | 1574 | 1575 | 1576 | 1577 | 1578 | 1579 | 1580 | 1581 | 1582 | 1583 | 1584 | 1585 | 1586 | 1587 | 1588 | 1589 | 1590 | 1591 | 1592 | 1593 | 1594 | 1595 | 1596 | 1597 | 1598 | 1599 | 1600 | 1601 | 1602 | 1603 | 1604 | 1605 | 1606 | 1607 | 1608 | 1609 | 1610 | 1611 | 1612 | 1613 | 1614 | 1615 | 1616 | 1617 | 1618 | Since Version 6.2.2 text objects can contain more than one line, 1619 | which will not be processed correctly with this version. 1620 | 1621 | 1622 | 1623 | -------------------------------------------------------------------------------- /Hardware/Safe Cracker Shield.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfunX/Safe_Cracker/50a5bc5d43b29f653f6a87770265c4c0cdb0ea94/Hardware/Safe Cracker Shield.pdf -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | SparkFun License Information 2 | ============================ 3 | 4 | SparkFun uses two different licenses for our files — one for hardware and one for code. 5 | 6 | Hardware 7 | --------- 8 | 9 | **SparkFun hardware is released under [Creative Commons Share-alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/).** 10 | 11 | Note: This is a human-readable summary of (and not a substitute for) the [license](http://creativecommons.org/licenses/by-sa/4.0/legalcode). 12 | 13 | You are free to: 14 | 15 | Share — copy and redistribute the material in any medium or format 16 | Adapt — remix, transform, and build upon the material 17 | for any purpose, even commercially. 18 | The licensor cannot revoke these freedoms as long as you follow the license terms. 19 | Under the following terms: 20 | 21 | Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 22 | ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. 23 | No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 24 | Notices: 25 | 26 | You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. 27 | No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. 28 | 29 | 30 | Code 31 | -------- 32 | 33 | **SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT).** 34 | 35 | The MIT License (MIT) 36 | 37 | Copyright (c) 2016 SparkFun Electronics 38 | 39 | Permission is hereby granted, free of charge, to any person obtaining a copy 40 | of this software and associated documentation files (the "Software"), to deal 41 | in the Software without restriction, including without limitation the rights 42 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 43 | copies of the Software, and to permit persons to whom the Software is 44 | furnished to do so, subject to the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be included in all 47 | copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 50 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 51 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 52 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 53 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 54 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 55 | SOFTWARE. 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SparkFun Safe Cracker 2 | =========================================================== 3 | 4 | ![SparkFun Safe Cracker](https://cdn.sparkfun.com/assets/learn_tutorials/6/5/1/Cracked_Safe.jpg) 5 | 6 | A robot with some tricks to open combination safes. 7 | 8 | We were able to crack our Craigslist safe in 40 minutes and 42 seconds! You can [re-watch the live cracking](https://www.youtube.com/watch?v=AsVSEHv2N4M) if you'd like. The magic moment occurs at [45:20](https://www.youtube.com/watch?v=AsVSEHv2N4M&t=45m10s) but start around [44:30](https://www.youtube.com/watch?v=AsVSEHv2N4M&t=44m30s) to get the full scope of what's going on. 9 | 10 | The full tutorial is [here](https://learn.sparkfun.com/tutorials/building-a-safe-cracking-robot). 11 | 12 | Repository Contents 13 | ------------------- 14 | 15 | * **/Hardware** - The layout of the Safe Cracker shield in EAGLE PCB 16 | * **/Firmware** - Source code that runs on the [RedBoard](https://www.sparkfun.com/products/13975) 17 | * **/3D** - STL files for the dial coupler, handle puller, nautilus horn, and electronics base plate 18 | * **live_crack_serial.log** - Output from the robot during the live stream. 19 | 20 | License Information 21 | ------------------- 22 | 23 | This project is _**open source**_! 24 | 25 | Please use, reuse, and modify these files as you see fit. Please maintain attribution to SparkFun Electronics and release anything derivative under the same license. 26 | 27 | Distributed as-is; no warranty is given. 28 | 29 | - Your friends at SparkX 30 | --------------------------------------------------------------------------------