diff --git a/examples/PINAndRFID/PINAndRFID.ino b/examples/PINAndRFID/PINAndRFID.ino index 812f94e..93b852a 100644 --- a/examples/PINAndRFID/PINAndRFID.ino +++ b/examples/PINAndRFID/PINAndRFID.ino @@ -3,7 +3,7 @@ * */ -//#define DEBUG_ENABLED +#define DEBUG_ENABLED #include #ifdef DEBUG_ENABLED @@ -29,7 +29,7 @@ void setup() { SERIAL_DEBUG_PORT.begin(115200); #endif #endif - if(usbConnected()) + //if(usbConnected()) { Lock.debug(SERIAL_DEBUG_PORT); } diff --git a/examples/PINbuttonAndRFID/PINbuttonAndRFID.ino b/examples/PINbuttonAndRFID/PINbuttonAndRFID.ino new file mode 100644 index 0000000..a3e9024 --- /dev/null +++ b/examples/PINbuttonAndRFID/PINbuttonAndRFID.ino @@ -0,0 +1,77 @@ +/* + * Example sketch for the lock prop, using Tap Code, PIN and/or RFID to control the lock + * + */ + +#define DEBUG_ENABLED + +#include +#ifdef DEBUG_ENABLED + #if ARDUINO_USB_CDC_ON_BOOT == 1 + #define SERIAL_DEBUG_PORT Serial + #else + #define SERIAL_DEBUG_PORT USBSerial + #endif +#endif + +#ifdef DEBUG_ENABLED + uint32_t lastStats = 0; +#endif + +void setup() { + #ifdef DEBUG_ENABLED + #if defined(ARDUINO_ESP8266_WEMOS_D1MINI) + SERIAL_DEBUG_PORT.begin(115200, SERIAL_8N1, SERIAL_TX_ONLY); //Note SERIAL_TX_ONLY allows use of RX for a button + #elif defined(ARDUINO_ESP32C3_DEV) + #if ARDUINO_USB_CDC_ON_BOOT == 1 + SERIAL_DEBUG_PORT.begin(); + #else + SERIAL_DEBUG_PORT.begin(115200); + #endif + #endif + //if(usbConnected()) + { + Lock.debug(SERIAL_DEBUG_PORT); + } + #endif + Lock.enableButton2(); //Enable button 2 on default pin, which will be used to start 'lock picking' + Lock.enableRedLed(); //Enable the red LED on default pin + Lock.enableGreenLed(); //Enable the green LED on default pin + #ifndef DEBUG_ENABLED + Lock.enableBuzzer(); //Enable the buzzer on the default pin + #else + /* + if(usbConnected() == false) + { + Lock.enableBuzzer(); //Enable the buzzer on the default pin + } + */ + #endif + Lock.enableMatrixKeypad(); //Enable the matrix keypad on default pins + Lock.enableRFID(); //Enable RFID reader authorisation, CS on default pin, using default card sector + Lock.enableLockPicking(Lock.button2()); //Enable fast PIN entry if you push the 'lock picking' button + Lock.begin(); //Start the lock with a default access ID +} + +void loop() { + Lock.housekeeping(); //This is all you need for basic functionality and interactive behaviour baked into the library + #ifdef DEBUG_ENABLED + /* + if(millis() - lastStats > 5000) + { + lastStats = millis(); + SERIAL_DEBUG_PORT.print(F("Uptime: ")); + SERIAL_DEBUG_PORT.print(float(millis())/60000.0); + SERIAL_DEBUG_PORT.println(F(" minutes")); + } + */ + #endif +} + +bool usbConnected() +{ + //uint32_t DR_REG_USB_SERIAL_JTAG_BASE = 0x60043000; //see https://github.com/search?q=DR_REG_USB_SERIAL_JTAG_BASE&type=code + uint32_t USB_SERIAL_JTAG_FRAM_NUM_REG = (DR_REG_USB_SERIAL_JTAG_BASE + 0x24); + uint32_t *aa = (uint32_t*)USB_SERIAL_JTAG_FRAM_NUM_REG; + return (*aa) != 0; +} diff --git a/examples/PINbuttonAndRFID/data/css/normalize.css b/examples/PINbuttonAndRFID/data/css/normalize.css new file mode 100644 index 0000000..81c6f31 --- /dev/null +++ b/examples/PINbuttonAndRFID/data/css/normalize.css @@ -0,0 +1,427 @@ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} \ No newline at end of file diff --git a/examples/PINbuttonAndRFID/data/css/skeleton.css b/examples/PINbuttonAndRFID/data/css/skeleton.css new file mode 100644 index 0000000..b6ae4a9 --- /dev/null +++ b/examples/PINbuttonAndRFID/data/css/skeleton.css @@ -0,0 +1,419 @@ +/* +* Skeleton V2.0.4 +* Copyright 2014, Dave Gamache +* www.getskeleton.com +* Free to use under the MIT license. +* http://www.opensource.org/licenses/mit-license.php +* 12/29/2014 +*/ + + +/* Table of contents +–––––––––––––––––––––––––––––––––––––––––––––––––– +- Grid +- Base Styles +- Typography +- Links +- Buttons +- Forms +- Lists +- Code +- Tables +- Spacing +- Utilities +- Clearing +- Media Queries +*/ + + +/* Grid +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.container { + position: relative; + width: 100%; + max-width: 960px; + margin: 0 auto; + padding: 0 20px; + box-sizing: border-box; } +.column, +.columns { + width: 100%; + float: left; + box-sizing: border-box; } + +/* For devices larger than 400px */ +@media (min-width: 400px) { + .container { + width: 85%; + padding: 0; } +} + +/* For devices larger than 550px */ +@media (min-width: 550px) { + .container { + width: 80%; } + .column, + .columns { + margin-left: 4%; } + .column:first-child, + .columns:first-child { + margin-left: 0; } + + .one.column, + .one.columns { width: 4.66666666667%; } + .two.columns { width: 13.3333333333%; } + .three.columns { width: 22%; } + .four.columns { width: 30.6666666667%; } + .five.columns { width: 39.3333333333%; } + .six.columns { width: 48%; } + .seven.columns { width: 56.6666666667%; } + .eight.columns { width: 65.3333333333%; } + .nine.columns { width: 74.0%; } + .ten.columns { width: 82.6666666667%; } + .eleven.columns { width: 91.3333333333%; } + .twelve.columns { width: 100%; margin-left: 0; } + + .one-third.column { width: 30.6666666667%; } + .two-thirds.column { width: 65.3333333333%; } + + .one-half.column { width: 48%; } + + /* Offsets */ + .offset-by-one.column, + .offset-by-one.columns { margin-left: 8.66666666667%; } + .offset-by-two.column, + .offset-by-two.columns { margin-left: 17.3333333333%; } + .offset-by-three.column, + .offset-by-three.columns { margin-left: 26%; } + .offset-by-four.column, + .offset-by-four.columns { margin-left: 34.6666666667%; } + .offset-by-five.column, + .offset-by-five.columns { margin-left: 43.3333333333%; } + .offset-by-six.column, + .offset-by-six.columns { margin-left: 52%; } + .offset-by-seven.column, + .offset-by-seven.columns { margin-left: 60.6666666667%; } + .offset-by-eight.column, + .offset-by-eight.columns { margin-left: 69.3333333333%; } + .offset-by-nine.column, + .offset-by-nine.columns { margin-left: 78.0%; } + .offset-by-ten.column, + .offset-by-ten.columns { margin-left: 86.6666666667%; } + .offset-by-eleven.column, + .offset-by-eleven.columns { margin-left: 95.3333333333%; } + + .offset-by-one-third.column, + .offset-by-one-third.columns { margin-left: 34.6666666667%; } + .offset-by-two-thirds.column, + .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } + + .offset-by-one-half.column, + .offset-by-one-half.columns { margin-left: 52%; } + +} + + +/* Base Styles +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +/* NOTE +html is set to 62.5% so that all the REM measurements throughout Skeleton +are based on 10px sizing. So basically 1.5rem = 15px :) */ +html { + font-size: 62.5%; } +body { + font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ + line-height: 1.6; + font-weight: 400; + font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #222; } + + +/* Typography +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: 2rem; + font-weight: 300; } +h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;} +h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; } +h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; } +h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } +h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } +h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } + +/* Larger than phablet */ +@media (min-width: 550px) { + h1 { font-size: 5.0rem; } + h2 { font-size: 4.2rem; } + h3 { font-size: 3.6rem; } + h4 { font-size: 3.0rem; } + h5 { font-size: 2.4rem; } + h6 { font-size: 1.5rem; } +} + +p { + margin-top: 0; } + + +/* Links +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +a { + color: #1EAEDB; } +a:hover { + color: #0FA0CE; } + + +/* Buttons +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.button, +button, +input[type="submit"], +input[type="reset"], +input[type="button"] { + display: inline-block; + height: 38px; + width: 100%; + padding: 0 30px; + color: #555; + text-align: center; + font-size: 11px; + font-weight: 600; + line-height: 38px; + letter-spacing: .1rem; + text-transform: uppercase; + text-decoration: none; + white-space: nowrap; + background-color: transparent; + border-radius: 4px; + border: 1px solid #bbb; + cursor: pointer; + box-sizing: border-box; } +.button:hover, +button:hover, +input[type="submit"]:hover, +input[type="reset"]:hover, +input[type="button"]:hover, +.button:focus, +button:focus, +input[type="submit"]:focus, +input[type="reset"]:focus, +input[type="button"]:focus { + color: #333; + border-color: #888; + outline: 0; } +.button.button-primary, +button.button-primary, +input[type="submit"].button-primary, +input[type="reset"].button-primary, +input[type="button"].button-primary { + color: #FFF; + background-color: #33C3F0; + border-color: #33C3F0; } +.button.button-primary:hover, +button.button-primary:hover, +input[type="submit"].button-primary:hover, +input[type="reset"].button-primary:hover, +input[type="button"].button-primary:hover, +.button.button-primary:focus, +button.button-primary:focus, +input[type="submit"].button-primary:focus, +input[type="reset"].button-primary:focus, +input[type="button"].button-primary:focus { + color: #FFF; + background-color: #1EAEDB; + border-color: #1EAEDB; } + + +/* Forms +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +input[type="email"], +input[type="number"], +input[type="search"], +input[type="text"], +input[type="tel"], +input[type="url"], +input[type="password"], +textarea, +select { + height: 38px; + padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ + background-color: #fff; + border: 1px solid #D1D1D1; + border-radius: 4px; + box-shadow: none; + box-sizing: border-box; } +/* Removes awkward default styles on some inputs for iOS */ +input[type="email"], +input[type="number"], +input[type="search"], +input[type="text"], +input[type="tel"], +input[type="url"], +input[type="password"], +textarea { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; } +textarea { + min-height: 65px; + padding-top: 6px; + padding-bottom: 6px; } +input[type="email"]:focus, +input[type="number"]:focus, +input[type="search"]:focus, +input[type="text"]:focus, +input[type="tel"]:focus, +input[type="url"]:focus, +input[type="password"]:focus, +textarea:focus, +select:focus { + border: 1px solid #33C3F0; + outline: 0; } +label, +legend { + display: block; + margin-bottom: .5rem; + font-weight: 600; } +fieldset { + padding: 0; + border-width: 0; } +input[type="checkbox"], +input[type="radio"] { + display: inline; } +label > .label-body { + display: inline-block; + margin-left: .5rem; + font-weight: normal; } + + +/* Lists +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +ul { + list-style: circle inside; } +ol { + list-style: decimal inside; } +ol, ul { + padding-left: 0; + margin-top: 0; } +ul ul, +ul ol, +ol ol, +ol ul { + margin: 1.5rem 0 1.5rem 3rem; + font-size: 90%; } +li { + margin-bottom: 1rem; } + + +/* Code +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +code { + padding: .2rem .5rem; + margin: 0 .2rem; + font-size: 90%; + white-space: nowrap; + background: #F1F1F1; + border: 1px solid #E1E1E1; + border-radius: 4px; } +pre > code { + display: block; + padding: 1rem 1.5rem; + white-space: pre; } + + +/* Tables +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +th, +td { + padding: 12px 15px; + text-align: left; + border-bottom: 1px solid #E1E1E1; } +th:first-child, +td:first-child { + padding-left: 0; } +th:last-child, +td:last-child { + padding-right: 0; } + + +/* Spacing +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +button, +.button { + margin-bottom: 1rem; } +input, +textarea, +select, +fieldset { + margin-bottom: 1.5rem; } +pre, +blockquote, +dl, +figure, +table, +p, +ul, +ol, +form { + margin-bottom: 2.5rem; } + + +/* Utilities +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.u-full-width { + width: 100%; + box-sizing: border-box; } +.u-max-full-width { + max-width: 100%; + box-sizing: border-box; } +.u-pull-right { + float: right; } +.u-pull-left { + float: left; } + + +/* Misc +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +hr { + margin-top: 3rem; + margin-bottom: 3.5rem; + border-width: 0; + border-top: 1px solid #E1E1E1; } + + +/* Clearing +–––––––––––––––––––––––––––––––––––––––––––––––––– */ + +/* Self Clearing Goodness */ +.container:after, +.row:after, +.u-cf { + content: ""; + display: table; + clear: both; } + + +/* Media Queries +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +/* +Note: The best way to structure the use of media queries is to create the queries +near the relevant code. For example, if you wanted to change the styles for buttons +on small devices, paste the mobile query code up in the buttons section and style it +there. +*/ + + +/* Larger than mobile */ +@media (min-width: 400px) {} + +/* Larger than phablet (also point when grid becomes active) */ +@media (min-width: 550px) {} + +/* Larger than tablet */ +@media (min-width: 750px) {} + +/* Larger than desktop */ +@media (min-width: 1000px) {} + +/* Larger than Desktop HD */ +@media (min-width: 1200px) {} diff --git a/examples/PINbuttonAndRFID/data/images/favicon.png b/examples/PINbuttonAndRFID/data/images/favicon.png new file mode 100644 index 0000000..7a3c81c Binary files /dev/null and b/examples/PINbuttonAndRFID/data/images/favicon.png differ diff --git a/examples/PINbuttonAndRFID/data/zones.csv b/examples/PINbuttonAndRFID/data/zones.csv new file mode 100644 index 0000000..82b2e42 --- /dev/null +++ b/examples/PINbuttonAndRFID/data/zones.csv @@ -0,0 +1,461 @@ +"Africa/Abidjan","GMT0" +"Africa/Accra","GMT0" +"Africa/Addis_Ababa","EAT-3" +"Africa/Algiers","CET-1" +"Africa/Asmara","EAT-3" +"Africa/Bamako","GMT0" +"Africa/Bangui","WAT-1" +"Africa/Banjul","GMT0" +"Africa/Bissau","GMT0" +"Africa/Blantyre","CAT-2" +"Africa/Brazzaville","WAT-1" +"Africa/Bujumbura","CAT-2" +"Africa/Cairo","EET-2" +"Africa/Casablanca","<+01>-1" +"Africa/Ceuta","CET-1CEST,M3.5.0,M10.5.0/3" +"Africa/Conakry","GMT0" +"Africa/Dakar","GMT0" +"Africa/Dar_es_Salaam","EAT-3" +"Africa/Djibouti","EAT-3" +"Africa/Douala","WAT-1" +"Africa/El_Aaiun","<+01>-1" +"Africa/Freetown","GMT0" +"Africa/Gaborone","CAT-2" +"Africa/Harare","CAT-2" +"Africa/Johannesburg","SAST-2" +"Africa/Juba","CAT-2" +"Africa/Kampala","EAT-3" +"Africa/Khartoum","CAT-2" +"Africa/Kigali","CAT-2" +"Africa/Kinshasa","WAT-1" +"Africa/Lagos","WAT-1" +"Africa/Libreville","WAT-1" +"Africa/Lome","GMT0" +"Africa/Luanda","WAT-1" +"Africa/Lubumbashi","CAT-2" +"Africa/Lusaka","CAT-2" +"Africa/Malabo","WAT-1" +"Africa/Maputo","CAT-2" +"Africa/Maseru","SAST-2" +"Africa/Mbabane","SAST-2" +"Africa/Mogadishu","EAT-3" +"Africa/Monrovia","GMT0" +"Africa/Nairobi","EAT-3" +"Africa/Ndjamena","WAT-1" +"Africa/Niamey","WAT-1" +"Africa/Nouakchott","GMT0" +"Africa/Ouagadougou","GMT0" +"Africa/Porto-Novo","WAT-1" +"Africa/Sao_Tome","GMT0" +"Africa/Tripoli","EET-2" +"Africa/Tunis","CET-1" +"Africa/Windhoek","CAT-2" +"America/Adak","HST10HDT,M3.2.0,M11.1.0" +"America/Anchorage","AKST9AKDT,M3.2.0,M11.1.0" +"America/Anguilla","AST4" +"America/Antigua","AST4" +"America/Araguaina","<-03>3" +"America/Argentina/Buenos_Aires","<-03>3" +"America/Argentina/Catamarca","<-03>3" +"America/Argentina/Cordoba","<-03>3" +"America/Argentina/Jujuy","<-03>3" +"America/Argentina/La_Rioja","<-03>3" +"America/Argentina/Mendoza","<-03>3" +"America/Argentina/Rio_Gallegos","<-03>3" +"America/Argentina/Salta","<-03>3" +"America/Argentina/San_Juan","<-03>3" +"America/Argentina/San_Luis","<-03>3" +"America/Argentina/Tucuman","<-03>3" +"America/Argentina/Ushuaia","<-03>3" +"America/Aruba","AST4" +"America/Asuncion","<-04>4<-03>,M10.1.0/0,M3.4.0/0" +"America/Atikokan","EST5" +"America/Bahia","<-03>3" +"America/Bahia_Banderas","CST6CDT,M4.1.0,M10.5.0" +"America/Barbados","AST4" +"America/Belem","<-03>3" +"America/Belize","CST6" +"America/Blanc-Sablon","AST4" +"America/Boa_Vista","<-04>4" +"America/Bogota","<-05>5" +"America/Boise","MST7MDT,M3.2.0,M11.1.0" +"America/Cambridge_Bay","MST7MDT,M3.2.0,M11.1.0" +"America/Campo_Grande","<-04>4" +"America/Cancun","EST5" +"America/Caracas","<-04>4" +"America/Cayenne","<-03>3" +"America/Cayman","EST5" +"America/Chicago","CST6CDT,M3.2.0,M11.1.0" +"America/Chihuahua","MST7MDT,M4.1.0,M10.5.0" +"America/Costa_Rica","CST6" +"America/Creston","MST7" +"America/Cuiaba","<-04>4" +"America/Curacao","AST4" +"America/Danmarkshavn","GMT0" +"America/Dawson","MST7" +"America/Dawson_Creek","MST7" +"America/Denver","MST7MDT,M3.2.0,M11.1.0" +"America/Detroit","EST5EDT,M3.2.0,M11.1.0" +"America/Dominica","AST4" +"America/Edmonton","MST7MDT,M3.2.0,M11.1.0" +"America/Eirunepe","<-05>5" +"America/El_Salvador","CST6" +"America/Fortaleza","<-03>3" +"America/Fort_Nelson","MST7" +"America/Glace_Bay","AST4ADT,M3.2.0,M11.1.0" +"America/Godthab","<-03>3<-02>,M3.5.0/-2,M10.5.0/-1" +"America/Goose_Bay","AST4ADT,M3.2.0,M11.1.0" +"America/Grand_Turk","EST5EDT,M3.2.0,M11.1.0" +"America/Grenada","AST4" +"America/Guadeloupe","AST4" +"America/Guatemala","CST6" +"America/Guayaquil","<-05>5" +"America/Guyana","<-04>4" +"America/Halifax","AST4ADT,M3.2.0,M11.1.0" +"America/Havana","CST5CDT,M3.2.0/0,M11.1.0/1" +"America/Hermosillo","MST7" +"America/Indiana/Indianapolis","EST5EDT,M3.2.0,M11.1.0" +"America/Indiana/Knox","CST6CDT,M3.2.0,M11.1.0" +"America/Indiana/Marengo","EST5EDT,M3.2.0,M11.1.0" +"America/Indiana/Petersburg","EST5EDT,M3.2.0,M11.1.0" +"America/Indiana/Tell_City","CST6CDT,M3.2.0,M11.1.0" +"America/Indiana/Vevay","EST5EDT,M3.2.0,M11.1.0" +"America/Indiana/Vincennes","EST5EDT,M3.2.0,M11.1.0" +"America/Indiana/Winamac","EST5EDT,M3.2.0,M11.1.0" +"America/Inuvik","MST7MDT,M3.2.0,M11.1.0" +"America/Iqaluit","EST5EDT,M3.2.0,M11.1.0" +"America/Jamaica","EST5" +"America/Juneau","AKST9AKDT,M3.2.0,M11.1.0" +"America/Kentucky/Louisville","EST5EDT,M3.2.0,M11.1.0" +"America/Kentucky/Monticello","EST5EDT,M3.2.0,M11.1.0" +"America/Kralendijk","AST4" +"America/La_Paz","<-04>4" +"America/Lima","<-05>5" +"America/Los_Angeles","PST8PDT,M3.2.0,M11.1.0" +"America/Lower_Princes","AST4" +"America/Maceio","<-03>3" +"America/Managua","CST6" +"America/Manaus","<-04>4" +"America/Marigot","AST4" +"America/Martinique","AST4" +"America/Matamoros","CST6CDT,M3.2.0,M11.1.0" +"America/Mazatlan","MST7MDT,M4.1.0,M10.5.0" +"America/Menominee","CST6CDT,M3.2.0,M11.1.0" +"America/Merida","CST6CDT,M4.1.0,M10.5.0" +"America/Metlakatla","AKST9AKDT,M3.2.0,M11.1.0" +"America/Mexico_City","CST6CDT,M4.1.0,M10.5.0" +"America/Miquelon","<-03>3<-02>,M3.2.0,M11.1.0" +"America/Moncton","AST4ADT,M3.2.0,M11.1.0" +"America/Monterrey","CST6CDT,M4.1.0,M10.5.0" +"America/Montevideo","<-03>3" +"America/Montreal","EST5EDT,M3.2.0,M11.1.0" +"America/Montserrat","AST4" +"America/Nassau","EST5EDT,M3.2.0,M11.1.0" +"America/New_York","EST5EDT,M3.2.0,M11.1.0" +"America/Nipigon","EST5EDT,M3.2.0,M11.1.0" +"America/Nome","AKST9AKDT,M3.2.0,M11.1.0" +"America/Noronha","<-02>2" +"America/North_Dakota/Beulah","CST6CDT,M3.2.0,M11.1.0" +"America/North_Dakota/Center","CST6CDT,M3.2.0,M11.1.0" +"America/North_Dakota/New_Salem","CST6CDT,M3.2.0,M11.1.0" +"America/Nuuk","<-03>3<-02>,M3.5.0/-2,M10.5.0/-1" +"America/Ojinaga","MST7MDT,M3.2.0,M11.1.0" +"America/Panama","EST5" +"America/Pangnirtung","EST5EDT,M3.2.0,M11.1.0" +"America/Paramaribo","<-03>3" +"America/Phoenix","MST7" +"America/Port-au-Prince","EST5EDT,M3.2.0,M11.1.0" +"America/Port_of_Spain","AST4" +"America/Porto_Velho","<-04>4" +"America/Puerto_Rico","AST4" +"America/Punta_Arenas","<-03>3" +"America/Rainy_River","CST6CDT,M3.2.0,M11.1.0" +"America/Rankin_Inlet","CST6CDT,M3.2.0,M11.1.0" +"America/Recife","<-03>3" +"America/Regina","CST6" +"America/Resolute","CST6CDT,M3.2.0,M11.1.0" +"America/Rio_Branco","<-05>5" +"America/Santarem","<-03>3" +"America/Santiago","<-04>4<-03>,M9.1.6/24,M4.1.6/24" +"America/Santo_Domingo","AST4" +"America/Sao_Paulo","<-03>3" +"America/Scoresbysund","<-01>1<+00>,M3.5.0/0,M10.5.0/1" +"America/Sitka","AKST9AKDT,M3.2.0,M11.1.0" +"America/St_Barthelemy","AST4" +"America/St_Johns","NST3:30NDT,M3.2.0,M11.1.0" +"America/St_Kitts","AST4" +"America/St_Lucia","AST4" +"America/St_Thomas","AST4" +"America/St_Vincent","AST4" +"America/Swift_Current","CST6" +"America/Tegucigalpa","CST6" +"America/Thule","AST4ADT,M3.2.0,M11.1.0" +"America/Thunder_Bay","EST5EDT,M3.2.0,M11.1.0" +"America/Tijuana","PST8PDT,M3.2.0,M11.1.0" +"America/Toronto","EST5EDT,M3.2.0,M11.1.0" +"America/Tortola","AST4" +"America/Vancouver","PST8PDT,M3.2.0,M11.1.0" +"America/Whitehorse","MST7" +"America/Winnipeg","CST6CDT,M3.2.0,M11.1.0" +"America/Yakutat","AKST9AKDT,M3.2.0,M11.1.0" +"America/Yellowknife","MST7MDT,M3.2.0,M11.1.0" +"Antarctica/Casey","<+11>-11" +"Antarctica/Davis","<+07>-7" +"Antarctica/DumontDUrville","<+10>-10" +"Antarctica/Macquarie","AEST-10AEDT,M10.1.0,M4.1.0/3" +"Antarctica/Mawson","<+05>-5" +"Antarctica/McMurdo","NZST-12NZDT,M9.5.0,M4.1.0/3" +"Antarctica/Palmer","<-03>3" +"Antarctica/Rothera","<-03>3" +"Antarctica/Syowa","<+03>-3" +"Antarctica/Troll","<+00>0<+02>-2,M3.5.0/1,M10.5.0/3" +"Antarctica/Vostok","<+06>-6" +"Arctic/Longyearbyen","CET-1CEST,M3.5.0,M10.5.0/3" +"Asia/Aden","<+03>-3" +"Asia/Almaty","<+06>-6" +"Asia/Amman","EET-2EEST,M2.5.4/24,M10.5.5/1" +"Asia/Anadyr","<+12>-12" +"Asia/Aqtau","<+05>-5" +"Asia/Aqtobe","<+05>-5" +"Asia/Ashgabat","<+05>-5" +"Asia/Atyrau","<+05>-5" +"Asia/Baghdad","<+03>-3" +"Asia/Bahrain","<+03>-3" +"Asia/Baku","<+04>-4" +"Asia/Bangkok","<+07>-7" +"Asia/Barnaul","<+07>-7" +"Asia/Beirut","EET-2EEST,M3.5.0/0,M10.5.0/0" +"Asia/Bishkek","<+06>-6" +"Asia/Brunei","<+08>-8" +"Asia/Chita","<+09>-9" +"Asia/Choibalsan","<+08>-8" +"Asia/Colombo","<+0530>-5:30" +"Asia/Damascus","EET-2EEST,M3.5.5/0,M10.5.5/0" +"Asia/Dhaka","<+06>-6" +"Asia/Dili","<+09>-9" +"Asia/Dubai","<+04>-4" +"Asia/Dushanbe","<+05>-5" +"Asia/Famagusta","EET-2EEST,M3.5.0/3,M10.5.0/4" +"Asia/Gaza","EET-2EEST,M3.4.4/48,M10.5.5/1" +"Asia/Hebron","EET-2EEST,M3.4.4/48,M10.5.5/1" +"Asia/Ho_Chi_Minh","<+07>-7" +"Asia/Hong_Kong","HKT-8" +"Asia/Hovd","<+07>-7" +"Asia/Irkutsk","<+08>-8" +"Asia/Jakarta","WIB-7" +"Asia/Jayapura","WIT-9" +"Asia/Jerusalem","IST-2IDT,M3.4.4/26,M10.5.0" +"Asia/Kabul","<+0430>-4:30" +"Asia/Kamchatka","<+12>-12" +"Asia/Karachi","PKT-5" +"Asia/Kathmandu","<+0545>-5:45" +"Asia/Khandyga","<+09>-9" +"Asia/Kolkata","IST-5:30" +"Asia/Krasnoyarsk","<+07>-7" +"Asia/Kuala_Lumpur","<+08>-8" +"Asia/Kuching","<+08>-8" +"Asia/Kuwait","<+03>-3" +"Asia/Macau","CST-8" +"Asia/Magadan","<+11>-11" +"Asia/Makassar","WITA-8" +"Asia/Manila","PST-8" +"Asia/Muscat","<+04>-4" +"Asia/Nicosia","EET-2EEST,M3.5.0/3,M10.5.0/4" +"Asia/Novokuznetsk","<+07>-7" +"Asia/Novosibirsk","<+07>-7" +"Asia/Omsk","<+06>-6" +"Asia/Oral","<+05>-5" +"Asia/Phnom_Penh","<+07>-7" +"Asia/Pontianak","WIB-7" +"Asia/Pyongyang","KST-9" +"Asia/Qatar","<+03>-3" +"Asia/Qyzylorda","<+05>-5" +"Asia/Riyadh","<+03>-3" +"Asia/Sakhalin","<+11>-11" +"Asia/Samarkand","<+05>-5" +"Asia/Seoul","KST-9" +"Asia/Shanghai","CST-8" +"Asia/Singapore","<+08>-8" +"Asia/Srednekolymsk","<+11>-11" +"Asia/Taipei","CST-8" +"Asia/Tashkent","<+05>-5" +"Asia/Tbilisi","<+04>-4" +"Asia/Tehran","<+0330>-3:30<+0430>,J79/24,J263/24" +"Asia/Thimphu","<+06>-6" +"Asia/Tokyo","JST-9" +"Asia/Tomsk","<+07>-7" +"Asia/Ulaanbaatar","<+08>-8" +"Asia/Urumqi","<+06>-6" +"Asia/Ust-Nera","<+10>-10" +"Asia/Vientiane","<+07>-7" +"Asia/Vladivostok","<+10>-10" +"Asia/Yakutsk","<+09>-9" +"Asia/Yangon","<+0630>-6:30" +"Asia/Yekaterinburg","<+05>-5" +"Asia/Yerevan","<+04>-4" +"Atlantic/Azores","<-01>1<+00>,M3.5.0/0,M10.5.0/1" +"Atlantic/Bermuda","AST4ADT,M3.2.0,M11.1.0" +"Atlantic/Canary","WET0WEST,M3.5.0/1,M10.5.0" +"Atlantic/Cape_Verde","<-01>1" +"Atlantic/Faroe","WET0WEST,M3.5.0/1,M10.5.0" +"Atlantic/Madeira","WET0WEST,M3.5.0/1,M10.5.0" +"Atlantic/Reykjavik","GMT0" +"Atlantic/South_Georgia","<-02>2" +"Atlantic/Stanley","<-03>3" +"Atlantic/St_Helena","GMT0" +"Australia/Adelaide","ACST-9:30ACDT,M10.1.0,M4.1.0/3" +"Australia/Brisbane","AEST-10" +"Australia/Broken_Hill","ACST-9:30ACDT,M10.1.0,M4.1.0/3" +"Australia/Currie","AEST-10AEDT,M10.1.0,M4.1.0/3" +"Australia/Darwin","ACST-9:30" +"Australia/Eucla","<+0845>-8:45" +"Australia/Hobart","AEST-10AEDT,M10.1.0,M4.1.0/3" +"Australia/Lindeman","AEST-10" +"Australia/Lord_Howe","<+1030>-10:30<+11>-11,M10.1.0,M4.1.0" +"Australia/Melbourne","AEST-10AEDT,M10.1.0,M4.1.0/3" +"Australia/Perth","AWST-8" +"Australia/Sydney","AEST-10AEDT,M10.1.0,M4.1.0/3" +"Europe/Amsterdam","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Andorra","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Astrakhan","<+04>-4" +"Europe/Athens","EET-2EEST,M3.5.0/3,M10.5.0/4" +"Europe/Belgrade","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Berlin","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Bratislava","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Brussels","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Bucharest","EET-2EEST,M3.5.0/3,M10.5.0/4" +"Europe/Budapest","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Busingen","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Chisinau","EET-2EEST,M3.5.0,M10.5.0/3" +"Europe/Copenhagen","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Dublin","IST-1GMT0,M10.5.0,M3.5.0/1" +"Europe/Gibraltar","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Guernsey","GMT0BST,M3.5.0/1,M10.5.0" +"Europe/Helsinki","EET-2EEST,M3.5.0/3,M10.5.0/4" +"Europe/Isle_of_Man","GMT0BST,M3.5.0/1,M10.5.0" +"Europe/Istanbul","<+03>-3" +"Europe/Jersey","GMT0BST,M3.5.0/1,M10.5.0" +"Europe/Kaliningrad","EET-2" +"Europe/Kiev","EET-2EEST,M3.5.0/3,M10.5.0/4" +"Europe/Kirov","<+03>-3" +"Europe/Lisbon","WET0WEST,M3.5.0/1,M10.5.0" +"Europe/Ljubljana","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/London","GMT0BST,M3.5.0/1,M10.5.0" +"Europe/Luxembourg","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Madrid","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Malta","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Mariehamn","EET-2EEST,M3.5.0/3,M10.5.0/4" +"Europe/Minsk","<+03>-3" +"Europe/Monaco","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Moscow","MSK-3" +"Europe/Oslo","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Paris","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Podgorica","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Prague","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Riga","EET-2EEST,M3.5.0/3,M10.5.0/4" +"Europe/Rome","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Samara","<+04>-4" +"Europe/San_Marino","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Sarajevo","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Saratov","<+04>-4" +"Europe/Simferopol","MSK-3" +"Europe/Skopje","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Sofia","EET-2EEST,M3.5.0/3,M10.5.0/4" +"Europe/Stockholm","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Tallinn","EET-2EEST,M3.5.0/3,M10.5.0/4" +"Europe/Tirane","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Ulyanovsk","<+04>-4" +"Europe/Uzhgorod","EET-2EEST,M3.5.0/3,M10.5.0/4" +"Europe/Vaduz","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Vatican","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Vienna","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Vilnius","EET-2EEST,M3.5.0/3,M10.5.0/4" +"Europe/Volgograd","<+03>-3" +"Europe/Warsaw","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Zagreb","CET-1CEST,M3.5.0,M10.5.0/3" +"Europe/Zaporozhye","EET-2EEST,M3.5.0/3,M10.5.0/4" +"Europe/Zurich","CET-1CEST,M3.5.0,M10.5.0/3" +"Indian/Antananarivo","EAT-3" +"Indian/Chagos","<+06>-6" +"Indian/Christmas","<+07>-7" +"Indian/Cocos","<+0630>-6:30" +"Indian/Comoro","EAT-3" +"Indian/Kerguelen","<+05>-5" +"Indian/Mahe","<+04>-4" +"Indian/Maldives","<+05>-5" +"Indian/Mauritius","<+04>-4" +"Indian/Mayotte","EAT-3" +"Indian/Reunion","<+04>-4" +"Pacific/Apia","<+13>-13" +"Pacific/Auckland","NZST-12NZDT,M9.5.0,M4.1.0/3" +"Pacific/Bougainville","<+11>-11" +"Pacific/Chatham","<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45" +"Pacific/Chuuk","<+10>-10" +"Pacific/Easter","<-06>6<-05>,M9.1.6/22,M4.1.6/22" +"Pacific/Efate","<+11>-11" +"Pacific/Enderbury","<+13>-13" +"Pacific/Fakaofo","<+13>-13" +"Pacific/Fiji","<+12>-12<+13>,M11.2.0,M1.2.3/99" +"Pacific/Funafuti","<+12>-12" +"Pacific/Galapagos","<-06>6" +"Pacific/Gambier","<-09>9" +"Pacific/Guadalcanal","<+11>-11" +"Pacific/Guam","ChST-10" +"Pacific/Honolulu","HST10" +"Pacific/Kiritimati","<+14>-14" +"Pacific/Kosrae","<+11>-11" +"Pacific/Kwajalein","<+12>-12" +"Pacific/Majuro","<+12>-12" +"Pacific/Marquesas","<-0930>9:30" +"Pacific/Midway","SST11" +"Pacific/Nauru","<+12>-12" +"Pacific/Niue","<-11>11" +"Pacific/Norfolk","<+11>-11<+12>,M10.1.0,M4.1.0/3" +"Pacific/Noumea","<+11>-11" +"Pacific/Pago_Pago","SST11" +"Pacific/Palau","<+09>-9" +"Pacific/Pitcairn","<-08>8" +"Pacific/Pohnpei","<+11>-11" +"Pacific/Port_Moresby","<+10>-10" +"Pacific/Rarotonga","<-10>10" +"Pacific/Saipan","ChST-10" +"Pacific/Tahiti","<-10>10" +"Pacific/Tarawa","<+12>-12" +"Pacific/Tongatapu","<+13>-13" +"Pacific/Wake","<+12>-12" +"Pacific/Wallis","<+12>-12" +"Etc/GMT","GMT0" +"Etc/GMT-0","GMT0" +"Etc/GMT-1","<+01>-1" +"Etc/GMT-2","<+02>-2" +"Etc/GMT-3","<+03>-3" +"Etc/GMT-4","<+04>-4" +"Etc/GMT-5","<+05>-5" +"Etc/GMT-6","<+06>-6" +"Etc/GMT-7","<+07>-7" +"Etc/GMT-8","<+08>-8" +"Etc/GMT-9","<+09>-9" +"Etc/GMT-10","<+10>-10" +"Etc/GMT-11","<+11>-11" +"Etc/GMT-12","<+12>-12" +"Etc/GMT-13","<+13>-13" +"Etc/GMT-14","<+14>-14" +"Etc/GMT0","GMT0" +"Etc/GMT+0","GMT0" +"Etc/GMT+1","<-01>1" +"Etc/GMT+2","<-02>2" +"Etc/GMT+3","<-03>3" +"Etc/GMT+4","<-04>4" +"Etc/GMT+5","<-05>5" +"Etc/GMT+6","<-06>6" +"Etc/GMT+7","<-07>7" +"Etc/GMT+8","<-08>8" +"Etc/GMT+9","<-09>9" +"Etc/GMT+10","<-10>10" +"Etc/GMT+11","<-11>11" +"Etc/GMT+12","<-12>12" +"Etc/UCT","UTC0" +"Etc/UTC","UTC0" +"Etc/Greenwich","GMT0" +"Etc/Universal","UTC0" +"Etc/Zulu","UTC0" diff --git a/keywords.txt b/keywords.txt index b020bf7..b864eee 100644 --- a/keywords.txt +++ b/keywords.txt @@ -77,6 +77,10 @@ addCodeToDenyNextCard KEYWORD2 codeMatches KEYWORD2 clearEnteredCode KEYWORD2 +//Tap code related +enableLockPicking KEYWORD2 + + //Wi-Fi admin enableWiFiAdminInterface KEYWORD2 adminOpened KEYWORD2 \ No newline at end of file diff --git a/src/LarpHackableRfidLock.cpp b/src/LarpHackableRfidLock.cpp index 3f24c8f..537bd8a 100644 --- a/src/LarpHackableRfidLock.cpp +++ b/src/LarpHackableRfidLock.cpp @@ -35,6 +35,7 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::begin(uint8_t id) { debugStream_->print(F("Restart reason: ")); es32printResetReason(0); } + setupPowerSaving(); //Detect CPU speeds available if(LittleFS.begin()) { //This will fail if not formatted, but ESP8266 will format it for you then fail if(debugStream_ != nullptr) { debugStream_->println(F("LittleFS filesystem mounted")); @@ -90,27 +91,44 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::begin(uint8_t id) { } if(red_led_fitted_) { if(debugStream_ != nullptr) { - debugStream_->println(F("Initialising red LED")); + debugStream_->print(F("Initialising red LED on pin:")); + debugStream_->print(red_led_pin_); } //Only configure the red LED pin if it's NOT shared with a button if((button1_fitted_ == false || red_led_pin_ != button1_pin_) && (button2_fitted_ == false || red_led_pin_ != button2_pin_) && (button3_fitted_ == false || red_led_pin_ != button3_pin_)) { pinMode(red_led_pin_,OUTPUT); digitalWrite(red_led_pin_,red_led_pin_off_value_); + if(debugStream_ != nullptr) { + debugStream_->println(); + } + } else { + if(debugStream_ != nullptr) { + debugStream_->println(F(" shared")); + } } } if(green_led_fitted_) { if(debugStream_ != nullptr) { - debugStream_->println(F("Initialising green LED")); + debugStream_->print(F("Initialising green LED on pin:")); + debugStream_->print(green_led_pin_); } //Only configure the green LED pin if it's NOT shared with a button if((button1_fitted_ == false || green_led_pin_ != button1_pin_) && (button2_fitted_ == false || green_led_pin_ != button2_pin_) && (button3_fitted_ == false || green_led_pin_ != button3_pin_)) { pinMode(green_led_pin_,OUTPUT); digitalWrite(green_led_pin_,green_led_pin_off_value_); + if(debugStream_ != nullptr) { + debugStream_->println(); + } + } else { + if(debugStream_ != nullptr) { + debugStream_->println(F(" shared")); + } } } if(button1_fitted_ == true) { if(debugStream_ != nullptr) { - debugStream_->println(F("Initialising button 1")); + debugStream_->print(F("Initialising button 1 on pin:")); + debugStream_->println(button1_pin_); } if(button1_pin_ != tapCode_pin_) { pinMode(button1_pin_,INPUT_PULLUP); @@ -119,7 +137,8 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::begin(uint8_t id) { } if(button2_fitted_ == true) { if(debugStream_ != nullptr) { - debugStream_->println(F("Initialising button 2")); + debugStream_->print(F("Initialising button 2 on pin:")); + debugStream_->println(button2_pin_); } if(button2_pin_ != tapCode_pin_) { pinMode(button2_pin_,INPUT_PULLUP); @@ -128,7 +147,8 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::begin(uint8_t id) { } if(button3_fitted_ == true) { if(debugStream_ != nullptr) { - debugStream_->println(F("Initialising button 3")); + debugStream_->print(F("Initialising button 3 on pin:")); + debugStream_->println(button3_pin_); } if(button3_pin_ != tapCode_pin_) { pinMode(button3_pin_,INPUT_PULLUP); @@ -149,11 +169,16 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::begin(uint8_t id) { if(debugStream_ != nullptr) { debugStream_->println(F("Initialising buzzer")); } - pinMode(buzzer_pin_,OUTPUT); #if defined ESP8266 noTone(buzzer_pin_); #elif defined ESP32 - ledcSetup(buzzer_channel_, 440, 8); + #ifdef ESP_IDF_VERSION_MAJOR // IDF 4+ + #if ESP_IDF_VERSION_MAJOR == 4 + ledcSetup(buzzer_channel_, 440, 8); //Old setup function + #elif ESP_IDF_VERSION_MAJOR == 5 + ledcAttach(buzzer_pin_, 440, 14); + #endif + #endif pinMode(buzzer_pin_,OUTPUT); digitalWrite(buzzer_pin_, buzzer_pin_off_value_); #endif @@ -264,30 +289,7 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::enableWiFiClient(char* SSID, char* void ICACHE_FLASH_ATTR LarpHackableRfidLock::configureWifi() { //Configure WiFi WiFi.persistent(false); //Avoid flash wear - if(wifi_client_enabled_ == true && wifi_client_ssid_ != nullptr && wifi_client_psk_!=nullptr && wifi_ap_enabled_ == true && wifi_ap_ssid_ != nullptr && wifi_ap_psk_!=nullptr) { - WiFi.mode(WIFI_AP_STA); - if(debugStream_ != nullptr) { - debugStream_->println(F("WiFi mode:station & AP")); - } - } else if(wifi_client_enabled_ == true && wifi_client_ssid_ != nullptr && wifi_client_psk_!=nullptr) { - if(debugStream_ != nullptr) { - debugStream_->println(F("WiFi mode:station")); - } - WiFi.mode(WIFI_STA); - } else if(wifi_ap_enabled_ == true && wifi_ap_ssid_ != nullptr && wifi_ap_psk_!=nullptr) { - if(debugStream_ != nullptr) { - debugStream_->println(F("WiFi mode:AP")); - } - WiFi.mode(WIFI_AP); - } else { - if(debugStream_ != nullptr) { - debugStream_->println(F("WiFi mode:Disabled")); - WiFi.mode(WIFI_OFF); - #if defined(ESP8266) - WiFi.forceSleepBegin(); - #endif - } - } + setWiFiMode(); if(lock_name_.length() > 0 && (wifi_client_enabled_ == true || wifi_ap_enabled_ == true)) { #if defined(ESP32) WiFi.setHostname(lock_name_.c_str()); @@ -345,6 +347,7 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::configureWifi() { #elif defined(ESP32) if(WiFi.softAP(wifi_ap_ssid_.c_str(), wifi_ap_psk_.c_str(), wifi_ap_channel_, (wifi_ap_hidden_ == true ? 1 : 0), wifi_ap_maximum_clients_, false) == true) { #endif + wifi_ap_active_ = true; if(debugStream_ != nullptr) { debugStream_->print(F(" - success AP IP:")); debugStream_->println(WiFi.softAPIP()); @@ -376,6 +379,54 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::configureWifi() { } } +void ICACHE_FLASH_ATTR LarpHackableRfidLock::setWiFiMode() { + if(wifi_client_enabled_ == true && wifi_client_ssid_ != nullptr && wifi_client_psk_!=nullptr && wifi_ap_enabled_ == true && wifi_ap_ssid_ != nullptr && wifi_ap_psk_!=nullptr) { + WiFi.mode(WIFI_AP_STA); + if(debugStream_ != nullptr) { + debugStream_->println(F("WiFi mode:station & AP")); + } + } else if(wifi_client_enabled_ == true && wifi_client_ssid_ != nullptr && wifi_client_psk_!=nullptr) { + WiFi.mode(WIFI_STA); + if(debugStream_ != nullptr) { + debugStream_->println(F("WiFi mode:station")); + } + } else if(wifi_ap_enabled_ == true && wifi_ap_ssid_ != nullptr && wifi_ap_psk_!=nullptr) { + WiFi.mode(WIFI_AP); + if(debugStream_ != nullptr) { + debugStream_->println(F("WiFi mode:AP")); + } + } else { + WiFi.mode(WIFI_OFF); + #if defined(ESP8266) + WiFi.forceSleepBegin(); + #endif + if(debugStream_ != nullptr) { + debugStream_->println(F("WiFi mode:Disabled")); + } + } +} + +void ICACHE_FLASH_ATTR LarpHackableRfidLock::disableWiFiAp() { + if(wifi_client_enabled_ == true && wifi_client_ssid_ != nullptr && wifi_client_psk_!=nullptr && wifi_ap_enabled_ == true && wifi_ap_ssid_ != nullptr && wifi_ap_psk_!=nullptr) { + WiFi.mode(WIFI_STA); + if(debugStream_ != nullptr) { + debugStream_->println(F("WiFi mode: AP")); + } + } else { + if(debugStream_ != nullptr) { + debugStream_->println(F("WiFi mode:Disabled")); + WiFi.mode(WIFI_OFF); + #if defined(ESP8266) + WiFi.forceSleepBegin(); + #endif + } + } +} +void ICACHE_FLASH_ATTR LarpHackableRfidLock::reEnableWiFiAp() { + wifi_ap_inactivity_shutdown_timer_ = millis(); + wifi_ap_active_ = true; + setWiFiMode(); +} void ICACHE_FLASH_ATTR LarpHackableRfidLock::configureTimeServer() { configTime(0, 0, ntp_server_.c_str()); //Set server if(ntp_timezone_.length() > 0) { @@ -399,6 +450,8 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::loadDefaultConfiguration() { tap_code_authorise_ = tap_code_authorise_default_; tap_code_revoke_ = tap_code_revoke_default_; tap_code_revoke_all_ = tap_code_revoke_all_default_; + //Lock picking + lock_picking_enabled_ = lock_picking_enabled_default_; //Mesh Network mesh_network_enabled_ = mesh_network_enabled_default_; mesh_network_channel_ = mesh_network_channel_default_; @@ -415,6 +468,8 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::loadDefaultConfiguration() { //WiFi AP wifi_ap_enabled_ = wifi_ap_enabled_default_; wifi_ap_hidden_ = wifi_ap_hidden_default_; + wifi_ap_captive_portal_ = wifi_ap_captive_portal_default_; + wifi_ap_inactivity_shutdown_ = wifi_ap_inactivity_shutdown_default_; wifi_ap_ssid_ = wifi_ap_ssid_default_; wifi_ap_psk_ = wifi_ap_psk_default_; } @@ -455,6 +510,8 @@ bool ICACHE_FLASH_ATTR LarpHackableRfidLock::loadConfiguration(const char* filen tap_code_authorise_ = doc[tap_code_authorise_key_] | tap_code_authorise_default_; tap_code_revoke_ = doc[tap_code_revoke_key_] | tap_code_revoke_default_; tap_code_revoke_all_ = doc[tap_code_revoke_all_key_] | tap_code_revoke_all_default_; + //Lock picking + lock_picking_enabled_ = doc[lock_picking_enabled_key_] | lock_picking_enabled_default_; //PIN codes pin_entry_enabled_ = doc[pin_entry_enabled_key_] | pin_entry_enabled_default_; pin_to_open_ = doc[pin_to_open_key_] | pin_to_open_default_; @@ -482,6 +539,7 @@ bool ICACHE_FLASH_ATTR LarpHackableRfidLock::loadConfiguration(const char* filen //WiFi AP wifi_ap_enabled_ = doc[wifi_ap_enabled_key_] | wifi_ap_enabled_default_; wifi_ap_captive_portal_ = doc[wifi_ap_captive_portal_key_] | wifi_ap_captive_portal_default_; + wifi_ap_inactivity_shutdown_ = doc[wifi_ap_inactivity_shutdown_key_] | wifi_ap_inactivity_shutdown_default_; wifi_ap_hidden_ = doc[wifi_ap_hidden_key_] | wifi_ap_hidden_default_; wifi_ap_ssid_ = String(doc[wifi_ap_ssid_key_] | wifi_ap_ssid_default_); wifi_ap_psk_ = String(doc[wifi_ap_psk_key_] | wifi_ap_psk_default_); @@ -573,6 +631,9 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::displayCurrentConfiguration() { } else { debugStream_->println(F("")); } + //Lock Picking + debugStream_->print(F("Lock picking enabled:")); + debugStream_->println(lock_picking_enabled_); //PINs debugStream_->print(F("PIN enabled:")); debugStream_->println(pin_entry_enabled_); @@ -699,6 +760,8 @@ bool ICACHE_FLASH_ATTR LarpHackableRfidLock::saveConfiguration(const char* filen doc[tap_code_authorise_key_] = tap_code_authorise_; doc[tap_code_revoke_key_] = tap_code_revoke_; doc[tap_code_revoke_all_key_] = tap_code_revoke_all_; + //Lock picking + doc[lock_picking_enabled_key_] = lock_picking_enabled_; //PIN codes doc[pin_entry_enabled_key_] = pin_entry_enabled_; doc[pin_to_open_key_] = pin_to_open_; @@ -720,6 +783,7 @@ bool ICACHE_FLASH_ATTR LarpHackableRfidLock::saveConfiguration(const char* filen //WiFi AP doc[wifi_ap_enabled_key_] = wifi_ap_enabled_; doc[wifi_ap_captive_portal_key_] = wifi_ap_captive_portal_; + doc[wifi_ap_inactivity_shutdown_key_] = wifi_ap_inactivity_shutdown_; doc[wifi_ap_hidden_key_] = wifi_ap_hidden_; doc[wifi_ap_ssid_key_] = wifi_ap_ssid_; doc[wifi_ap_psk_key_] = wifi_ap_psk_; @@ -951,8 +1015,13 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::housekeeping(){ debugStream_->print(retrievedContent); debugStream_->println('"'); } - if(multi_factor_type_ == multiFactorOption::partnerOnly || multi_factor_type_ == multiFactorOption::buttonAndPartner || multi_factor_type_ == multiFactorOption::cardAndPartner || multi_factor_type_ == multiFactorOption::PINandPartner || multi_factor_type_ == multiFactorOption::cardPINandPartner) { - if((retrievedContent.equals("Ready to open") || retrievedContent.equals("Open")) && multi_factor_partner_state_ == false) { + if(multi_factor_type_ == multiFactorOption::partnerOnly || + multi_factor_type_ == multiFactorOption::buttonAndPartner || + multi_factor_type_ == multiFactorOption::cardAndPartner || + multi_factor_type_ == multiFactorOption::PINandPartner || + multi_factor_type_ == multiFactorOption::cardPINandPartner) { + if((retrievedContent.equals("Ready to open") || + retrievedContent.equals("Open")) && multi_factor_partner_state_ == false) { multi_factor_partner_state_ = true; last_multi_factor_interaction_ = millis(); mesh_partner_advertisement_interval_ = mesh_partner_advertisement_fast_interval_; @@ -1000,9 +1069,12 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::housekeeping(){ Serial.println(housekeepingdebug++); //4 #endif if(green_led_fitted_) { - if(green_led_state_ == true && green_led_on_time_ > 0 && millis() - green_led_state_last_changed_ > green_led_on_time_) { //Switch off the red LED after green_led_on_time_ + if(green_led_state_ == true && green_led_on_time_ > 0 && millis() - green_led_state_last_changed_ > green_led_on_time_) { //Switch off the green LED after green_led_on_time_ greenLedOff(); } + if(green_led_state_ == false && green_led_off_time_ > 0 && millis() - green_led_state_last_changed_ > green_led_off_time_) { //Switch on the green LED after green_led_off_time_ + greenLedOn(red_led_on_time_, green_led_off_time_); + } } #ifdef HOUSEKEEPING_DEBUG Serial.println(housekeepingdebug++); //5 @@ -1050,19 +1122,25 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::housekeeping(){ #ifdef HOUSEKEEPING_DEBUG Serial.println(housekeepingdebug++); //9 #endif - if(tap_code_enabled_ == true && tapCode_ != nullptr) { //Only run the tap code sections if enabled + if(tap_code_enabled_ == true && tapCode_ != nullptr) { //Only run the tap code sections if enabled //Only read TapCode if the pin is not in use for something else, red/green buttons are sometimes shared with an LED if((red_led_state_ == false || tapCode_pin_ != red_led_pin_) && (green_led_state_ == false || tapCode_pin_ != green_led_pin_)) { tapCode_->read(); } } //Manage buttons - if(button1_fitted_ == true && button1_pin_ != tapCode_pin_ && button1_ != nullptr) { //Tap code reads the button separately + if(button1_fitted_ == true && button1_pin_ != tapCode_pin_ && button1_ != nullptr) { //Tap code reads the button separately //Only read button if the pin is not in use for something else, red/green buttons are sometimes shared with an LED if((red_led_state_ == false || button1_pin_ != red_led_pin_) && (green_led_state_ == false || button1_pin_ != green_led_pin_)) { this->button1_->read(); //Update EasyButton } if(this->button1_->isPressed() && button1_pressed_ == false) { + speedUpProcessor(); + /* + if(wifi_ap_enabled_ == true && wifi_ap_inactivity_shutdown_ == true && wifi_ap_active_ == false) { + reEnableWiFiAp(); + } + */ button1_pressed_ = true; if(multi_factor_enabled_ == true && multi_factor_type_ == multiFactorOption::buttonAndPartner && open_button_pin_ == button1_pin_) { multi_factor_button_state_ = true; @@ -1090,11 +1168,17 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::housekeeping(){ #ifdef HOUSEKEEPING_DEBUG Serial.println(housekeepingdebug++); //10 #endif - if(button2_fitted_ == true && button2_pin_ != tapCode_pin_&& button2_ != nullptr) { //Tap code reads the button separately - if((red_led_state_ == false || button2_pin_ != red_led_pin_) && (green_led_state_ == false || button2_pin_ != green_led_pin_)) { + if(button2_fitted_ == true && button2_pin_ != tapCode_pin_&& button2_ != nullptr) { //Tap code reads the button separately + if((green_led_state_ == false || button2_pin_ != green_led_pin_) && (red_led_state_ == false || button2_pin_ != red_led_pin_)) { this->button2_->read(); //Update EasyButton } if(this->button2_->isPressed() && button2_pressed_ == false) { + speedUpProcessor(); + /* + if(wifi_ap_enabled_ == true && wifi_ap_inactivity_shutdown_ == true && wifi_ap_active_ == false) { + reEnableWiFiAp(); + } + */ button2_pressed_ = true; if(multi_factor_enabled_ == true && multi_factor_type_ == multiFactorOption::buttonAndPartner && open_button_pin_ == button2_pin_) { multi_factor_button_state_ = true; @@ -1106,8 +1190,20 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::housekeeping(){ debugStream_->println(F("Button 2 pushed for multi-factor authorisation")); } } else { - if(debugStream_ != nullptr) { - debugStream_->println(F("Button 2 pushed")); + if(lock_picking_enabled_ == true && lock_picking_pin_ == button2_pin_) { + lock_picking_timer_ = millis(); + if(lock_picking_active_ == false) { + lock_picking_active_ = true; + if(debugStream_ != nullptr) { + debugStream_->println(F("Lock picking started with button 2")); + } + } + } + else + { + if(debugStream_ != nullptr) { + debugStream_->println(F("Button 2 pushed")); + } } } } @@ -1121,11 +1217,17 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::housekeeping(){ #ifdef HOUSEKEEPING_DEBUG Serial.println(housekeepingdebug++); //11 #endif - if(button3_fitted_ == true && button3_pin_ != tapCode_pin_&& button3_ != nullptr) { //Tap code reads the button separately + if(button3_fitted_ == true && button3_pin_ != tapCode_pin_&& button3_ != nullptr) { //Tap code reads the button separately if((red_led_state_ == false || button3_pin_ != red_led_pin_) && (green_led_state_ == false || button3_pin_ != green_led_pin_)) { this->button3_->read(); //Update EasyButton } if(this->button3_->isPressed() && button3_pressed_ == false) { + speedUpProcessor(); + /* + if(wifi_ap_enabled_ == true && wifi_ap_inactivity_shutdown_ == true && wifi_ap_active_ == false) { + reEnableWiFiAp(); + } + */ button3_pressed_ = true; if(multi_factor_enabled_ == true && multi_factor_type_ == multiFactorOption::buttonAndPartner && open_button_pin_ == button3_pin_) { multi_factor_button_state_ = true; @@ -1137,8 +1239,20 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::housekeeping(){ debugStream_->println(F("Button 3 pushed for multi-factor authorisation")); } } else { - if(debugStream_ != nullptr) { - debugStream_->println(F("Button 3 pushed")); + if(lock_picking_enabled_ == true && lock_picking_pin_ == button3_pin_) { + lock_picking_timer_ = millis(); + if(lock_picking_active_ == false) { + lock_picking_active_ = true; + if(debugStream_ != nullptr) { + debugStream_->println(F("Lock picking started with button 3")); + } + } + } + else + { + if(debugStream_ != nullptr) { + debugStream_->println(F("Button 3 pushed")); + } } } } @@ -1152,6 +1266,13 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::housekeeping(){ #ifdef HOUSEKEEPING_DEBUG Serial.println(housekeepingdebug++); //12 #endif + //Manage lock picking + if(lock_picking_active_ == true && millis() - lock_picking_timer_ > 60E3) { + lock_picking_active_ = false; + if(debugStream_ != nullptr) { + debugStream_->println(F("Lock picking timed out")); + } + } //Manage keypad matrix #ifdef HOUSEKEEPING_DEBUG Serial.println(housekeepingdebug++); //13 @@ -1159,6 +1280,7 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::housekeeping(){ if(matrix_fitted_ == true && matrix_ != nullptr) { char matrixKeypress = matrix_->getKey(); if(matrixKeypress){ + speedUpProcessor(); if(debugStream_ != nullptr) { debugStream_->print(F("Keypad: ")); debugStream_->println(String(matrixKeypress)); @@ -1167,7 +1289,9 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::housekeeping(){ last_pin_entry_ = millis(); entered_pin_[pin_length_++] = matrixKeypress; entered_pin_[pin_length_] = char(0); //Null terminate the string - positiveFeedback(); + if(lock_picking_active_ == false) { + positiveFeedback(); + } } if(pin_length_ == abs_max_pin_length_ || pin_length_ == max_pin_length_) { if(debugStream_ != nullptr) { @@ -1278,6 +1402,7 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::housekeeping(){ #endif if(rfid_reader_intialised_ == true) { if(cardPresent() && cardChanged()) { + speedUpProcessor(); if(rfidReaderState == RFID_READER_NORMAL) { if(lock_state_ == lock_state_option_::normal) { if(cardAuthorised(accessId()) == true) { @@ -1401,13 +1526,31 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::housekeeping(){ #ifdef HOUSEKEEPING_DEBUG Serial.println(housekeepingdebug++); #endif - if(WiFi.softAPgetStationNum() != current_number_of_clients_) { - current_number_of_clients_ = WiFi.softAPgetStationNum(); - if(debugStream_ != nullptr) { - debugStream_->print(F("Number of WiFi clients:")); - debugStream_->print(current_number_of_clients_); - debugStream_->print('/'); - debugStream_->println(maximum_number_of_clients_); + if(wifi_ap_active_ == true) { + uint8_t temp_number_of_clients_ = WiFi.softAPgetStationNum(); + if(temp_number_of_clients_ != current_number_of_clients_) { + current_number_of_clients_ = temp_number_of_clients_; + wifi_ap_inactivity_shutdown_timer_ = millis(); + if(debugStream_ != nullptr) { + debugStream_->print(F("Number of WiFi clients:")); + debugStream_->print(current_number_of_clients_); + debugStream_->print('/'); + debugStream_->println(maximum_number_of_clients_); + } + } + speedUpProcessor(); + if(wifi_ap_inactivity_shutdown_ == true && + wifi_client_enabled_ == false && + millis() - wifi_ap_inactivity_shutdown_timer_ > 150e3) { //Shut down inactive AP to save power + wifi_ap_active_ = false; + if(debugStream_ != nullptr) { + debugStream_->println(F("WiFi AP inactive, shutting AP down")); + } + disableWiFiAp(); + } + } else { + if(millis() - last_processor_wake_up_ > 10E3) { + slowDownProcessor(); } } #ifdef HOUSEKEEPING_DEBUG @@ -1463,6 +1606,111 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::housekeeping(){ lock_state_ = lock_state_option_::normal; } } +void ICACHE_FLASH_ATTR LarpHackableRfidLock::setupPowerSaving() { + //Adjust the minimum speed based off type of module/crystal, there are limitations + if(getXtalFrequencyMhz() == 40) { + maximum_cpu_speed_ = 160; + working_cpu_speed_ = 80; + minimum_cpu_speed_ = 10; + } else if(getXtalFrequencyMhz() == 26) { + working_cpu_speed_ = 26; + minimum_cpu_speed_ = 13; + } else if(getXtalFrequencyMhz() == 24) { + working_cpu_speed_ = 24; + minimum_cpu_speed_ = 12; + } else { + minimum_cpu_speed_ = 80; + } + if(debugStream_ != nullptr) { + debugStream_->print(F("Maxiumum CPU speed:")); + debugStream_->println(maximum_cpu_speed_); + debugStream_->print(F("Working CPU speed: ")); + debugStream_->println(working_cpu_speed_); + debugStream_->print(F("Minimum CPU speed: ")); + debugStream_->println(minimum_cpu_speed_); + /* + Serial.print(F("Processor slow timeout: ")); + if(processorSlowTimeout > 0) + { + Serial.println(processorSlowTimeout); + } + else + { + Serial.println(F("disabled")); + } + Serial.print(F("Processor sleep timeout: ")); + if(processorSleepTimeout > 0) + { + Serial.println(processorSleepTimeout); + } + else + { + Serial.println(F("disabled")); + } + Serial.print(F("Processor deep sleep threshold: ")); + if(processorDeepSleepThreshold > 0) + { + Serial.println(processorDeepSleepThreshold); + } + else + { + Serial.println(F("disabled")); + } + Serial.print(F("Amplifier snooze timeout: ")); + if(amplifierSnoozeTimeout > 0) + { + Serial.println(amplifierSnoozeTimeout); + } + else + { + Serial.println(F("disabled")); + } + Serial.print(F("Screen snooze timeout: ")); + if(screenSnoozeTimeout > 0) + { + Serial.println(screenSnoozeTimeout); + } + else + { + Serial.println(F("disabled")); + } + */ + } +} +void ICACHE_FLASH_ATTR LarpHackableRfidLock::speedUpProcessor() { + if(wifi_ap_active_ == true || wifi_client_enabled_ == true) { + if(current_number_of_clients_ > 0) { + speedUpProcessor(maximum_cpu_speed_); + } else { + speedUpProcessor(minimum_cpu_wifi_speed_); + } + } else { + speedUpProcessor(working_cpu_speed_); + } +} +void ICACHE_FLASH_ATTR LarpHackableRfidLock::speedUpProcessor(uint8_t speed) { + if(speed != getCpuFrequencyMhz()) { + last_processor_wake_up_ = millis(); + if(debugStream_ != nullptr) { + debugStream_->print(F("Setting processor speed: ")); + debugStream_->print(speed); + debugStream_->println(F("Mhz")); + } + setCpuFrequencyMhz(speed); + processor_awake_ = true; + } +} +void ICACHE_FLASH_ATTR LarpHackableRfidLock::slowDownProcessor() { + if(processor_awake_ == true) { + if(debugStream_ != nullptr) { + debugStream_->print(F("Slowing processor to minimum: ")); + debugStream_->print(minimum_cpu_speed_); + debugStream_->println(F("Mhz")); + } + processor_awake_ = false; + setCpuFrequencyMhz(minimum_cpu_speed_); + } +} void ICACHE_FLASH_ATTR LarpHackableRfidLock::resetMultifactorState() { @@ -1711,7 +1959,7 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::enableGreenLed(uint8_t pin, uint8_t green_led_pin_off_value_ = HIGH; } } -void ICACHE_FLASH_ATTR LarpHackableRfidLock::greenLedOn(uint32_t on_time) { +void ICACHE_FLASH_ATTR LarpHackableRfidLock::greenLedOn(uint32_t on_time, uint32_t off_time) { if(green_led_fitted_) { if(green_led_state_ == false) { //A shared pin needs changing to an output @@ -1731,12 +1979,14 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::greenLedOn(uint32_t on_time) { } } } + green_led_on_time_= on_time; + green_led_off_time_= off_time; } } -void ICACHE_FLASH_ATTR LarpHackableRfidLock::greenLedOff(uint32_t off_time) { +void ICACHE_FLASH_ATTR LarpHackableRfidLock::greenLedOff(uint32_t off_time, uint32_t on_time) { if(green_led_fitted_) { if(green_led_state_ == true) { - if((button1_fitted_ && green_led_pin_ == button1_pin_ )||(button2_fitted_ && green_led_pin_ == button2_pin_ )||(button1_fitted_ && green_led_pin_ == button3_pin_)) { + if((button1_fitted_ && green_led_pin_ == button1_pin_ )||(button2_fitted_ && green_led_pin_ == button2_pin_ )||(button3_fitted_ && green_led_pin_ == button3_pin_)) { if(green_led_pin_off_value_ == HIGH) { pinMode(green_led_pin_, INPUT_PULLUP); } else { @@ -1756,6 +2006,8 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::greenLedOff(uint32_t off_time) { } } } + green_led_on_time_= on_time; + green_led_off_time_= off_time; } } /* @@ -1780,8 +2032,15 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::buzzerOn(uint16_t frequency, uint32 #if defined ESP8266 tone(buzzer_pin_, frequency, on_time); #elif defined ESP32 - ledcAttachPin(buzzer_pin_, 0); - ledcWriteTone(buzzer_channel_, frequency); + #ifdef ESP_IDF_VERSION_MAJOR // IDF 4+ + #if ESP_IDF_VERSION_MAJOR == 4 + ledcAttachPin(buzzer_pin_, buzzer_channel_); //Old method + ledcWriteTone(buzzer_channel_, frequency); + #elif ESP_IDF_VERSION_MAJOR == 5 + ledcAttach(buzzer_pin_, 440, 14); + ledcWriteTone(buzzer_pin_, frequency); + #endif + #endif #endif if(debugStream_ != nullptr) { @@ -1796,8 +2055,15 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::buzzerOff() { #if defined ESP8266 noTone(buzzer_pin_); #elif defined ESP32 - ledcWriteTone(buzzer_channel_, 0); - ledcDetachPin(buzzer_pin_); + #ifdef ESP_IDF_VERSION_MAJOR // IDF 4+ + #if ESP_IDF_VERSION_MAJOR == 4 + ledcWriteTone(buzzer_channel_, 0); + ledcDetachPin(buzzer_pin_); //Old method + #elif ESP_IDF_VERSION_MAJOR == 5 + ledcWriteTone(buzzer_pin_, 0); + ledcDetach(buzzer_pin_); + #endif + #endif pinMode(buzzer_pin_, OUTPUT); digitalWrite(buzzer_pin_, buzzer_pin_off_value_); #endif @@ -1832,6 +2098,12 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::allow() { lock_state_ = lock_state_option_::allow; last_lock_state_change_ = millis(); next_lock_state_change_ = decisionLedOnTime; + if(lock_picking_active_ == true) { + if(debugStream_ != nullptr) { + debugStream_->println(F("Lock picking succesful")); + } + lock_picking_active_ = false; //Cancel any ongoing lockpick attempt + } if(debugStream_ != nullptr) { debugStream_->println(F("Allowing access")); } @@ -1860,18 +2132,29 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::deny() { if(lock_state_ != lock_state_option_::deny) { lock_state_ = lock_state_option_::deny; last_lock_state_change_ = millis(); - next_lock_state_change_ = decisionLedOnTime; if(debugStream_ != nullptr) { debugStream_->println(F("Denying access")); } - if(buzzer_fitted_) { - buzzerOn(musicalTone[6], decisionBuzzerOnTime); - } - if(red_led_fitted_) { - redLedOn(decisionLedOnTime); - } - if(green_led_fitted_) { - greenLedOff(); + if(lock_picking_active_ == true) { + lock_picking_timer_ = millis(); + next_lock_state_change_ = feedbackLedOnTime; + if(red_led_fitted_) { + redLedOn(feedbackLedOnTime); + } + if(green_led_fitted_) { + greenLedOff(); + } + } else { + next_lock_state_change_ = decisionLedOnTime; + if(buzzer_fitted_) { + buzzerOn(musicalTone[6], decisionBuzzerOnTime); + } + if(red_led_fitted_) { + redLedOn(decisionLedOnTime); + } + if(green_led_fitted_) { + greenLedOff(); + } } } } @@ -1880,6 +2163,12 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::unseal() { lock_state_ = lock_state_option_::unsealed; last_lock_state_change_ = millis(); next_lock_state_change_ = 0; //Stick in this state + if(lock_picking_active_ == true) { + if(debugStream_ != nullptr) { + debugStream_->println(F("Lock picking succesful")); + } + lock_picking_active_ = false; //Cancel any ongoing lockpick attempt + } if(debugStream_ != nullptr) { debugStream_->println(F("Unsealing lock")); } @@ -1899,6 +2188,12 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::seal() { lock_state_ = lock_state_option_::sealed; last_lock_state_change_ = millis(); next_lock_state_change_ = 0; //Stick in this state + if(lock_picking_active_ == true) { + if(debugStream_ != nullptr) { + debugStream_->println(F("Lock picking succesful")); + } + lock_picking_active_ = false; //Cancel any ongoing lockpick attempt + } if(debugStream_ != nullptr) { debugStream_->println(F("Sealing lock")); } @@ -1918,6 +2213,12 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::normal() { lock_state_ = lock_state_option_::normal; last_lock_state_change_ = millis(); next_lock_state_change_ = 0; //Stick in this state + if(lock_picking_active_ == true) { + if(debugStream_ != nullptr) { + debugStream_->println(F("Lock picking succesful")); + } + lock_picking_active_ = false; //Cancel any ongoing lockpick attempt + } if(debugStream_ != nullptr) { debugStream_->println(F("Setting lock to normal state")); } @@ -2081,6 +2382,13 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::clearEnteredCode() { tapCode_->reset(); } } +/* + * Lock Picking, which just enables fast entry of PIN codes + */ +void ICACHE_FLASH_ATTR LarpHackableRfidLock::enableLockPicking(uint8_t pin) { + lock_picking_enabled_ = true; + lock_picking_pin_ = pin; +} /* * Game */ diff --git a/src/LarpHackableRfidLock.h b/src/LarpHackableRfidLock.h index 3e7dbf3..688ac5e 100644 --- a/src/LarpHackableRfidLock.h +++ b/src/LarpHackableRfidLock.h @@ -11,6 +11,7 @@ #include #define WEBUI_IN_LIBRARY +//#define ENABLE_OTA_UPDATE //#define DRD //#define HOUSEKEEPING_DEBUG #ifdef HOUSEKEEPING_DEBUG @@ -48,11 +49,13 @@ #endif #include //Mesh network #include //Filesystem admin routines -#ifdef WEBUI_IN_LIBRARY +#if defined(WEBUI_IN_LIBRARY) //#include //Used for Web UI #include //Used for Web UI #include //Used for Web UI - #include //Used for OTA update + #if defined(ENABLE_OTA_UPDATE) + #include //Used for OTA update + #endif #endif #include //Used for captive portal in Web UI #include //Used for configuration storage @@ -96,9 +99,9 @@ class LarpHackableRfidLock { void enableButton3(uint8_t pin = 8); //Enable button 3 on the default pin #endif #if defined(ARDUINO_ESP8266_WEMOS_D1MINI) - void enableServo(uint8_t pin = D4); //Enable button 3 on the default pin + void enableServo(uint8_t pin = D4); //Enable servo on the default pin #elif defined(ARDUINO_ESP32C3_DEV) - void enableServo(uint8_t pin = 18); //Enable button 3 on the default pin + void enableServo(uint8_t pin = 18); //Enable servo on the default pin #endif uint8_t button1(); //Return the GPIO used for button 1 uint8_t button2(); //Return the GPIO used for button 2 @@ -148,8 +151,8 @@ class LarpHackableRfidLock { #elif defined(ARDUINO_ESP32C3_DEV) void enableGreenLed(uint8_t pin = 9, uint8_t onLevel = LOW); //Enable the red LED on the default pin #endif - void greenLedOn(uint32_t on_time = 0); //Switch on green LED. On time is 0 for permanent or otherwise in ms. - void greenLedOff(uint32_t off_time = 0); //Switch off green LED. Off time is 0 for permanent or otherwise in ms. + void greenLedOn(uint32_t on_time = 0, uint32_t off_time = 0); //Switch on green LED. On time is 0 for permanent or otherwise in ms. + void greenLedOff(uint32_t off_time = 0, uint32_t on_time = 0); //Switch off green LED. Off time is 0 for permanent or otherwise in ms. //Buzzer #if defined(ARDUINO_ESP8266_WEMOS_D1MINI) void enableBuzzer(uint8_t pin = D1); //Enable the buzzer on the default pin @@ -197,6 +200,12 @@ class LarpHackableRfidLock { #elif defined(ARDUINO_ESP32C3_DEV) void enableTapCode(uint8_t pin = 9); //Enables the use of tap code on the lock #endif + //Lockpicking mechanic using PIN entry + #if defined(ARDUINO_ESP8266_WEMOS_D1MINI) + void enableLockPicking(uint8_t pin = RX); //Enables the use of 'lock picking' on the lock + #elif defined(ARDUINO_ESP32C3_DEV) + void enableLockPicking(uint8_t pin = 9); //Enables the use of 'lock picking' on the lock + #endif bool codeEntered(); //Has a code been entered? bool addCodeToOpen(char* codeToAdd); bool addCodeToLock(char* codeToAdd); @@ -334,7 +343,11 @@ class LarpHackableRfidLock { uint8_t tapCode_pin_ = 255; //GPIO pin used for TapCode TapCode* tapCode_ = nullptr; //Pointer to the tap code class, if enabled static const uint8_t abs_max_tap_code_length_ = 16; - + //Lock picking + bool lock_picking_enabled_ = false; //Is 'lock picking' enabled + uint8_t lock_picking_pin_ = 255; //GPIO pin used for 'lock picking' + uint32_t lock_picking_timer_ = 0; //Used to time out lock picking + bool lock_picking_active_ = false; //True if lock picking is underway //RFID related TrivialRFIDauthorisation* rfid_ = nullptr; //Pointer to the RFID wrapper class uint8_t rfid_authorisation_sector_ = 0; //Sector where the RFID authorisation block is stored @@ -347,14 +360,43 @@ class LarpHackableRfidLock { uint16_t connectionRetryFrequency = 1000; //Interval between retries in ms //IP/connection - void configureWifi(); //Start WiFi + void setWiFiMode(); //Enable WiFi + void disableWiFiAp(); //Disable WiFi AP to save power + void reEnableWiFiAp(); //Re-enable WiFi AP on activity + void configureWifi(); //Configure WiFi related things like the hostname void printIpStatus_(); //Debug info about the IP status of the ESP void printConnectionStatus_(); //Debug info about the connection status of the ESP void configureTimeServer(); //Configure the time server //Client connection uint8_t current_number_of_clients_ = 0; uint8_t maximum_number_of_clients_ = 4; - + + //Power saving + #if ESP_IDF_VERSION_MAJOR > 3 // IDF 4+ + #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 + uint8_t maximum_cpu_speed_ = 240; //Maximum CPU speed for ESP32 + #elif CONFIG_IDF_TARGET_ESP32S2 + uint8_t maximum_cpu_speed_ = 240; //Maximum CPU speed for ESP32S2 + #elif CONFIG_IDF_TARGET_ESP32S3 + uint8_t maximum_cpu_speed_ = 240; //Maximum CPU speed for ESP32S3 + #elif CONFIG_IDF_TARGET_ESP32C3 + uint8_t maximum_cpu_speed_ = 160; //Maximum CPU speed for ESP32C3 + #else + #error Target CONFIG_IDF_TARGET is not supported + #endif + #else // ESP32 Before IDF 4.0 + uint8_t maximum_cpu_speed_ = 240; //Maximum CPU speed + #endif + uint8_t minimum_cpu_wifi_speed_ = 80; //Working CPU speed with WiFi active + uint8_t working_cpu_speed_ = 80; //Working CPU speed + uint8_t minimum_cpu_speed_ = 10; //Minimum CPU speed + bool processor_awake_ = true; //Is the CPU awake or at minimum speed + uint32_t last_processor_wake_up_ = 0; //Track when processor was last fully woken up + void setupPowerSaving(); //Pick suitable CPU speeds based on type of ESP32 + void speedUpProcessor(); //Wake up the processor to do stuff + void speedUpProcessor(uint8_t speed); //Wake up the processor to do stuff + void slowDownProcessor(); //Slow down the processor to save power + //Web admin UI #ifdef WEBUI_IN_LIBRARY bool web_admin_server_enabled_ = true; //Is the web admin server enabled @@ -480,17 +522,20 @@ class LarpHackableRfidLock { static void webAdminWiFiClientSettingsPageCallback(); //Callback for Wifi client settings page static void web_admin_wifi_client_settings_page_save_button_pressed(EmbAJAXPushButton*); //WiFi AP - EmbAJAXPage<50>* web_admin_wifi_ap_settings_page_ = nullptr; - EmbAJAXBase* web_admin_wifi_ap_settings_page_elements_[50]; + static const uint8_t web_admin_wifi_ap_settings_page_element_count_ = 60; + EmbAJAXPage* web_admin_wifi_ap_settings_page_ = nullptr; + EmbAJAXBase* web_admin_wifi_ap_settings_page_elements_[web_admin_wifi_ap_settings_page_element_count_]; EmbAJAXStatic* web_admin_wifi_ap_settings_page_static0_ = nullptr; EmbAJAXStatic* web_admin_wifi_ap_settings_page_static1_ = nullptr; EmbAJAXStatic* web_admin_wifi_ap_settings_page_static2_ = nullptr; EmbAJAXStatic* web_admin_wifi_ap_settings_page_static3_ = nullptr; EmbAJAXStatic* web_admin_wifi_ap_settings_page_static4_ = nullptr; EmbAJAXStatic* web_admin_wifi_ap_settings_page_static5_ = nullptr; + EmbAJAXStatic* web_admin_wifi_ap_settings_page_static6_ = nullptr; EmbAJAXCheckButton* web_admin_wifi_ap_settings_page_check0_ = nullptr; EmbAJAXCheckButton* web_admin_wifi_ap_settings_page_check1_ = nullptr; EmbAJAXCheckButton* web_admin_wifi_ap_settings_page_check2_ = nullptr; + EmbAJAXCheckButton* web_admin_wifi_ap_settings_page_check3_ = nullptr; EmbAJAXTextInput<64>* web_admin_wifi_ap_settings_page_text0_ = nullptr; EmbAJAXTextInput<64>* web_admin_wifi_ap_settings_page_text1_ = nullptr; EmbAJAXPushButton* web_admin_wifi_ap_settings_page_save_button_ = nullptr; @@ -513,14 +558,17 @@ class LarpHackableRfidLock { static void webAdminM2mMeshSettingsPageCallback(); //Callback for m2mMesh settings page static void web_admin_m2mMesh_settings_page_save_button_pressed(EmbAJAXPushButton*); //PINs - EmbAJAXPage<40>* web_admin_pin_settings_page_ = nullptr; - EmbAJAXBase* web_admin_pin_settings_page_elements_[40]; + static const uint8_t web_admin_pin_settings_page_element_count_ = 50; + EmbAJAXPage* web_admin_pin_settings_page_ = nullptr; + EmbAJAXBase* web_admin_pin_settings_page_elements_[web_admin_pin_settings_page_element_count_]; EmbAJAXStatic* web_admin_pin_settings_page_static0_ = nullptr; EmbAJAXStatic* web_admin_pin_settings_page_static1_ = nullptr; EmbAJAXStatic* web_admin_pin_settings_page_static2_ = nullptr; EmbAJAXStatic* web_admin_pin_settings_page_static3_ = nullptr; EmbAJAXStatic* web_admin_pin_settings_page_static4_ = nullptr; + EmbAJAXStatic* web_admin_pin_settings_page_static5_ = nullptr; EmbAJAXCheckButton* web_admin_pin_settings_page_check0_ = nullptr; + EmbAJAXCheckButton* web_admin_pin_settings_page_check1_ = nullptr; EmbAJAXTextInput* web_admin_pin_settings_page_text0_ = nullptr; EmbAJAXTextInput* web_admin_pin_settings_page_text1_ = nullptr; EmbAJAXTextInput* web_admin_pin_settings_page_text2_ = nullptr; @@ -545,17 +593,18 @@ class LarpHackableRfidLock { static void webAdminTapCodeSettingsPageCallback(); static void webAdminTapCodeSettingsPageSaveButtonPressed(EmbAJAXPushButton*); //RFID provisioning - EmbAJAXPage<66>* web_admin_rfid_settings_page_ = nullptr; - EmbAJAXBase* web_admin_rfid_settings_page_elements_[66]; + static const uint8_t web_admin_rfid_settings_page_elements_count_ = 53; + EmbAJAXPage* web_admin_rfid_settings_page_ = nullptr; + EmbAJAXBase* web_admin_rfid_settings_page_elements_[web_admin_rfid_settings_page_elements_count_]; EmbAJAXStatic* web_admin_rfid_settings_page_static0_ = nullptr; EmbAJAXStatic* web_admin_rfid_settings_page_static1_ = nullptr; EmbAJAXStatic* web_admin_rfid_settings_page_static2_ = nullptr; EmbAJAXStatic* web_admin_rfid_settings_page_static3_ = nullptr; - EmbAJAXStatic* web_admin_rfid_settings_page_static4_ = nullptr; + //EmbAJAXStatic* web_admin_rfid_settings_page_static4_ = nullptr; EmbAJAXStatic* web_admin_rfid_settings_page_static5_ = nullptr; EmbAJAXPushButton* web_admin_rfid_settings_page_button0_ = nullptr; EmbAJAXPushButton* web_admin_rfid_settings_page_button1_ = nullptr; - EmbAJAXPushButton* web_admin_rfid_settings_page_button2_ = nullptr; + //EmbAJAXPushButton* web_admin_rfid_settings_page_button2_ = nullptr; EmbAJAXCheckButton* web_admin_rfid_settings_page_check0_ = nullptr; EmbAJAXPushButton* web_admin_rfid_settings_page_save_button_ = nullptr; void createWebAdminRFIDPage(); //Create the web admin control page @@ -674,6 +723,9 @@ class LarpHackableRfidLock { //Tap code char tap_code_enabled_key_[15] = "tapCodeEnabled"; bool tap_code_enabled_default_ = false; + //Lock Picking + char lock_picking_enabled_key_[19] = "lockPickingEnabled"; + bool lock_picking_enabled_default_ = false; //Tap code positive feedback bool tap_code_positive_feedback_ = false; char tap_code_positive_feedback_key_[24] = "tapCodePositiveFeedback"; @@ -741,6 +793,7 @@ class LarpHackableRfidLock { bool wifi_ap_enabled_ = false; String wifi_ap_enabled_key_ = PSTR("wifiApEnabled"); bool wifi_ap_enabled_default_ = true; + bool wifi_ap_active_ = false; //Is the WiFi AP SSID hidden bool wifi_ap_hidden_ = false; String wifi_ap_hidden_key_ = PSTR("wifiApHidden"); @@ -749,6 +802,11 @@ class LarpHackableRfidLock { bool wifi_ap_captive_portal_ = true; String wifi_ap_captive_portal_key_ = PSTR("wifiApCaptivePortal"); bool wifi_ap_captive_portal_default_ = true; + //Is the WiFi AP shutdown on inactivity + bool wifi_ap_inactivity_shutdown_ = false; + String wifi_ap_inactivity_shutdown_key_ = PSTR("wifiApInactiveShutdown"); + bool wifi_ap_inactivity_shutdown_default_ = false; + uint32_t wifi_ap_inactivity_shutdown_timer_ = 0; //WiFi AP channel uint8_t wifi_ap_channel_ = 1; //WiFi AP max clients diff --git a/src/webAdminInterface.cpp b/src/webAdminInterface.cpp index 83a65cf..f139a5b 100644 --- a/src/webAdminInterface.cpp +++ b/src/webAdminInterface.cpp @@ -18,7 +18,9 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::startWebAdminInterface() { webAdminServer->on("/css/skeleton.css", HTTP_GET, [](AsyncWebServerRequest *request) {request->send(LittleFS, "/css/skeleton.css", "text/css");}); webAdminServer->on("/images/favicon.png", HTTP_GET, [](AsyncWebServerRequest *request) {request->send(LittleFS, "/images/favicon.png", "image/png");}); webAdminServer->on("/config", HTTP_GET, [](AsyncWebServerRequest *request) {request->send(LittleFS, Lock.configuration_file_, "text/json");}); - AsyncElegantOTA.begin(webAdminServer); + #if defined(ENABLE_OTA_UPDATE) + AsyncElegantOTA.begin(webAdminServer); + #endif webAdminServer->begin(); } @@ -369,6 +371,7 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::webAdminGeneralSettingsSaveButtonPr } Lock.lock_name_ = String(Lock.web_admin_general_settings_page_text0_->value()); Lock.lock_access_group_ = String(Lock.web_admin_general_settings_page_text1_->value()).toInt(); + Lock.web_admin_general_settings_page_save_button_->setVisible(false); Lock.save_configuration_soon_ = millis(); } } @@ -469,6 +472,7 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::web_admin_wifi_client_settings_page Lock.ntp_client_enabled_ = Lock.web_admin_wifi_client_settings_page_check1_->isChecked(); Lock.ntp_server_ = String(Lock.web_admin_wifi_client_settings_page_text2_->value()); Lock.ntp_timezone_ = String(Lock.web_admin_wifi_client_settings_page_text3_->value()); + Lock.web_admin_wifi_client_settings_page_save_button_->setVisible(false); Lock.save_configuration_soon_ = millis(); } } @@ -480,9 +484,11 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::createWebAdminWiFiAPSettingsPage() web_admin_wifi_ap_settings_page_static3_ = new EmbAJAXStatic(PSTR("")); web_admin_wifi_ap_settings_page_static4_ = new EmbAJAXStatic(PSTR("WiFi AP hidden")); web_admin_wifi_ap_settings_page_static5_ = new EmbAJAXStatic(PSTR("Captive portal")); + web_admin_wifi_ap_settings_page_static6_ = new EmbAJAXStatic(PSTR("Shutdown if inactive for 5 minutes")); web_admin_wifi_ap_settings_page_check0_ = new EmbAJAXCheckButton(PSTR("check0")); web_admin_wifi_ap_settings_page_check1_ = new EmbAJAXCheckButton(PSTR("check1")); web_admin_wifi_ap_settings_page_check2_ = new EmbAJAXCheckButton(PSTR("check2")); + web_admin_wifi_ap_settings_page_check3_ = new EmbAJAXCheckButton(PSTR("check3")); web_admin_wifi_ap_settings_page_text0_ = new EmbAJAXTextInput<64>(PSTR("ssid")); web_admin_wifi_ap_settings_page_text1_ = new EmbAJAXTextInput<64>(PSTR("psk")); web_admin_wifi_ap_settings_page_save_button_ = new EmbAJAXPushButton(PSTR("save"), PSTR("Save"), web_admin_wifi_ap_settings_page_save_button_pressed); @@ -519,6 +525,16 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::createWebAdminWiFiAPSettingsPage() web_admin_wifi_ap_settings_page_elements_[index++] = endLabel_; web_admin_wifi_ap_settings_page_elements_[index++] = endDiv_; web_admin_wifi_ap_settings_page_elements_[index++] = endDiv_; + web_admin_wifi_ap_settings_page_elements_[index++] = startRowDiv_; + web_admin_wifi_ap_settings_page_elements_[index++] = startTwelveColumnDiv_; + web_admin_wifi_ap_settings_page_elements_[index++] = startLabel_; + web_admin_wifi_ap_settings_page_elements_[index++] = web_admin_wifi_ap_settings_page_check3_; + web_admin_wifi_ap_settings_page_elements_[index++] = startLabelSpan_; + web_admin_wifi_ap_settings_page_elements_[index++] = web_admin_wifi_ap_settings_page_static6_; + web_admin_wifi_ap_settings_page_elements_[index++] = endSpan_; + web_admin_wifi_ap_settings_page_elements_[index++] = endLabel_; + web_admin_wifi_ap_settings_page_elements_[index++] = endDiv_; + web_admin_wifi_ap_settings_page_elements_[index++] = endDiv_; web_admin_wifi_ap_settings_page_elements_[index++] = startRowDiv_; web_admin_wifi_ap_settings_page_elements_[index++] = startSixColumnDiv_; web_admin_wifi_ap_settings_page_elements_[index++] = web_admin_wifi_ap_settings_page_static2_; @@ -538,11 +554,12 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::createWebAdminWiFiAPSettingsPage() web_admin_wifi_ap_settings_page_elements_[index++] = endDiv_; web_admin_wifi_ap_settings_page_elements_[index++] = endDiv_; web_admin_wifi_ap_settings_page_elements_[index++] = endDiv_; - web_admin_wifi_ap_settings_page_ = new EmbAJAXPage<50>(web_admin_wifi_ap_settings_page_elements_, PSTR("WiFi AP"), web_admin_header_includes_); + web_admin_wifi_ap_settings_page_ = new EmbAJAXPage(web_admin_wifi_ap_settings_page_elements_, PSTR("WiFi AP"), web_admin_header_includes_); webAdminInterface_->installPage(web_admin_wifi_ap_settings_page_, PSTR("/wifiap"), webAdminWiFiAPSettingsPageCallback); web_admin_wifi_ap_settings_page_check0_->setChecked(wifi_ap_enabled_); web_admin_wifi_ap_settings_page_check1_->setChecked(wifi_ap_hidden_); web_admin_wifi_ap_settings_page_check2_->setChecked(wifi_ap_captive_portal_); + web_admin_wifi_ap_settings_page_check3_->setChecked(wifi_ap_inactivity_shutdown_); web_admin_wifi_ap_settings_page_text0_->setValue(wifi_ap_ssid_.c_str()); web_admin_wifi_ap_settings_page_text1_->setValue(wifi_ap_psk_.c_str()); } @@ -561,8 +578,10 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::web_admin_wifi_ap_settings_page_sav Lock.wifi_ap_enabled_ = Lock.web_admin_wifi_ap_settings_page_check0_->isChecked(); Lock.wifi_ap_hidden_ = Lock.web_admin_wifi_ap_settings_page_check1_->isChecked(); Lock.wifi_ap_captive_portal_ = Lock.web_admin_wifi_ap_settings_page_check2_->isChecked(); + Lock.wifi_ap_inactivity_shutdown_ = Lock.web_admin_wifi_ap_settings_page_check3_->isChecked(); Lock.wifi_ap_ssid_ = String(Lock.web_admin_wifi_ap_settings_page_text0_->value()); Lock.wifi_ap_psk_ = String(Lock.web_admin_wifi_ap_settings_page_text1_->value()); + Lock.web_admin_wifi_ap_settings_page_save_button_->setVisible(false); Lock.save_configuration_soon_ = millis(); } } @@ -659,6 +678,7 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::web_admin_m2mMesh_settings_page_sav Lock.mesh_network_enabled_ = Lock.web_admin_m2mMesh_settings_page_check0_->isChecked(); Lock.mesh_network_channel_ = Lock.web_admin_m2mMesh_settings_page_select0_->selectedOption() + 1; Lock.mesh_network_id_ = String(Lock.web_admin_m2mMesh_settings_page_text0_->value()).toInt(); + Lock.web_admin_m2mMesh_settings_page_save_button_->setVisible(false); Lock.save_configuration_soon_ = millis(); } } @@ -669,7 +689,9 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::createWebAdminPINSettingsPage() { web_admin_pin_settings_page_static2_ = new EmbAJAXStatic(PSTR("")); web_admin_pin_settings_page_static3_ = new EmbAJAXStatic(PSTR("")); web_admin_pin_settings_page_static4_ = new EmbAJAXStatic(PSTR("")); + web_admin_pin_settings_page_static5_ = new EmbAJAXStatic(PSTR("'Lock picking' (fast PIN entry) enabled")); web_admin_pin_settings_page_check0_ = new EmbAJAXCheckButton(PSTR("check0")); + web_admin_pin_settings_page_check1_ = new EmbAJAXCheckButton(PSTR("check1")); web_admin_pin_settings_page_text0_ = new EmbAJAXTextInput(PSTR("open")); web_admin_pin_settings_page_text1_ = new EmbAJAXTextInput(PSTR("seal")); web_admin_pin_settings_page_text2_ = new EmbAJAXTextInput(PSTR("unseal")); @@ -689,6 +711,16 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::createWebAdminPINSettingsPage() { web_admin_pin_settings_page_elements_[index++] = endLabel_; web_admin_pin_settings_page_elements_[index++] = endDiv_; web_admin_pin_settings_page_elements_[index++] = endDiv_; + web_admin_pin_settings_page_elements_[index++] = startRowDiv_; + web_admin_pin_settings_page_elements_[index++] = startTwelveColumnDiv_; + web_admin_pin_settings_page_elements_[index++] = startLabel_; + web_admin_pin_settings_page_elements_[index++] = web_admin_pin_settings_page_check1_; + web_admin_pin_settings_page_elements_[index++] = startLabelSpan_; + web_admin_pin_settings_page_elements_[index++] = web_admin_pin_settings_page_static5_; + web_admin_pin_settings_page_elements_[index++] = endSpan_; + web_admin_pin_settings_page_elements_[index++] = endLabel_; + web_admin_pin_settings_page_elements_[index++] = endDiv_; + web_admin_pin_settings_page_elements_[index++] = endDiv_; web_admin_pin_settings_page_elements_[index++] = startRowDiv_; web_admin_pin_settings_page_elements_[index++] = startTwelveColumnDiv_; web_admin_pin_settings_page_elements_[index++] = web_admin_pin_settings_page_static2_; @@ -716,9 +748,10 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::createWebAdminPINSettingsPage() { web_admin_pin_settings_page_elements_[index++] = endDiv_; web_admin_pin_settings_page_elements_[index++] = endDiv_; web_admin_pin_settings_page_elements_[index++] = endDiv_; - web_admin_pin_settings_page_ = new EmbAJAXPage<40>(web_admin_pin_settings_page_elements_, PSTR("PIN settings"), web_admin_header_includes_); + web_admin_pin_settings_page_ = new EmbAJAXPage(web_admin_pin_settings_page_elements_, PSTR("PIN settings"), web_admin_header_includes_); webAdminInterface_->installPage(web_admin_pin_settings_page_, PSTR("/pins"), webAdminPINSettingsPageCallback); web_admin_pin_settings_page_check0_->setChecked(pin_entry_enabled_); + web_admin_pin_settings_page_check1_->setChecked(lock_picking_enabled_); web_admin_pin_settings_page_text0_->setValue(pin_to_open_.c_str()); web_admin_pin_settings_page_text1_->setValue(pin_to_seal_.c_str()); web_admin_pin_settings_page_text2_->setValue(pin_to_unseal_.c_str()); @@ -736,9 +769,11 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::web_admin_pin_settings_page_save_bu } if(Lock.configuration_changed_ == true) { Lock.pin_entry_enabled_ = Lock.web_admin_pin_settings_page_check0_->isChecked(); + Lock.lock_picking_enabled_ = Lock.web_admin_pin_settings_page_check1_->isChecked(); Lock.pin_to_open_ = String(Lock.web_admin_pin_settings_page_text0_->value()); Lock.pin_to_seal_ = String(Lock.web_admin_pin_settings_page_text1_->value()); Lock.pin_to_unseal_ = String(Lock.web_admin_pin_settings_page_text2_->value()); + Lock.web_admin_pin_settings_page_save_button_->setVisible(false); Lock.save_configuration_soon_ = millis(); } } @@ -819,6 +854,7 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::webAdminTapCodeSettingsPageSaveButt Lock.tap_code_open_ = String(Lock.web_admin_tap_code_settings_page_text0_->value()); Lock.tap_code_seal_ = String(Lock.web_admin_tap_code_settings_page_text1_->value()); Lock.tap_code_unseal_ = String(Lock.web_admin_tap_code_settings_page_text2_->value()); + Lock.web_admin_tap_code_settings_page_save_button_->setVisible(false); Lock.save_configuration_soon_ = millis(); } } @@ -826,13 +862,13 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::webAdminTapCodeSettingsPageSaveButt void ICACHE_FLASH_ATTR LarpHackableRfidLock::createWebAdminRFIDPage() { web_admin_rfid_settings_page_static0_ = new EmbAJAXStatic(PSTR("

RFID provisioning

")); web_admin_rfid_settings_page_static1_ = new EmbAJAXStatic(PSTR("

These buttons will change the access for the NEXT card presented at the lock. This can be used for provisioning cards for this lock. Note the authorisation applies to all devices in the same group as this lock.

")); - web_admin_rfid_settings_page_static2_ = new EmbAJAXStatic(PSTR("")); - web_admin_rfid_settings_page_static3_ = new EmbAJAXStatic(PSTR("")); - web_admin_rfid_settings_page_static4_ = new EmbAJAXStatic(PSTR("")); + web_admin_rfid_settings_page_static2_ = new EmbAJAXStatic(PSTR("")); + web_admin_rfid_settings_page_static3_ = new EmbAJAXStatic(PSTR("")); + //web_admin_rfid_settings_page_static4_ = new EmbAJAXStatic(PSTR("")); web_admin_rfid_settings_page_static5_ = new EmbAJAXStatic(PSTR("RFID card entry enabled")); web_admin_rfid_settings_page_button0_ = new EmbAJAXPushButton(PSTR("button0"), PSTR("Authorise"), web_admin_rfid_settings_page_button0_pressed); web_admin_rfid_settings_page_button1_ = new EmbAJAXPushButton(PSTR("button1"), PSTR("Revoke"), web_admin_rfid_settings_page_button1_pressed); - web_admin_rfid_settings_page_button2_ = new EmbAJAXPushButton(PSTR("button2"), PSTR("Wipe ALL"), web_admin_rfid_settings_page_button2_pressed); + //web_admin_rfid_settings_page_button2_ = new EmbAJAXPushButton(PSTR("button2"), PSTR("Wipe ALL"), web_admin_rfid_settings_page_button2_pressed); web_admin_rfid_settings_page_check0_ = new EmbAJAXCheckButton(PSTR("check0")); web_admin_rfid_settings_page_save_button_ = new EmbAJAXPushButton(PSTR("save"), PSTR("Save"), webAdminRFIDSettingsPageSaveButtonPressed); web_admin_rfid_settings_page_save_button_->setVisible(false); @@ -881,6 +917,7 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::createWebAdminRFIDPage() { web_admin_rfid_settings_page_elements_[index++] = nbsp_; web_admin_rfid_settings_page_elements_[index++] = endDiv_; web_admin_rfid_settings_page_elements_[index++] = endDiv_; + /* web_admin_rfid_settings_page_elements_[index++] = startRowDiv_; web_admin_rfid_settings_page_elements_[index++] = startTwelveColumnDiv_; web_admin_rfid_settings_page_elements_[index++] = web_admin_rfid_settings_page_static4_; @@ -894,6 +931,7 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::createWebAdminRFIDPage() { web_admin_rfid_settings_page_elements_[index++] = nbsp_; web_admin_rfid_settings_page_elements_[index++] = endDiv_; web_admin_rfid_settings_page_elements_[index++] = endDiv_; + */ web_admin_rfid_settings_page_elements_[index++] = startRowDiv_; web_admin_rfid_settings_page_elements_[index++] = startTenColumnDiv_; web_admin_rfid_settings_page_elements_[index++] = nbsp_; @@ -903,7 +941,7 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::createWebAdminRFIDPage() { web_admin_rfid_settings_page_elements_[index++] = endDiv_; web_admin_rfid_settings_page_elements_[index++] = endDiv_; web_admin_rfid_settings_page_elements_[index++] = endDiv_; - web_admin_rfid_settings_page_ = new EmbAJAXPage<66>(web_admin_rfid_settings_page_elements_, PSTR("RFID"), web_admin_header_includes_); + web_admin_rfid_settings_page_ = new EmbAJAXPage(web_admin_rfid_settings_page_elements_, PSTR("RFID"), web_admin_header_includes_); webAdminInterface_->installPage(web_admin_rfid_settings_page_, PSTR("/rfid"), webAdminRFIDPageCallback); web_admin_rfid_settings_page_check0_->setChecked(rfid_authorisation_enabled_); } @@ -919,12 +957,14 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::web_admin_rfid_settings_page_button } Lock.rfidReaderState = Lock.RFID_READER_DENY_NEXT_CARD; } +/* void ICACHE_FLASH_ATTR LarpHackableRfidLock::web_admin_rfid_settings_page_button2_pressed(EmbAJAXPushButton*) { if(Lock.debugStream_ != nullptr) { Lock.debugStream_->println(F("web_admin_rfid_settings_page_button2_pressed wipe")); } Lock.rfidReaderState = Lock.RFID_READER_WIPE_NEXT_CARD; } +*/ void ICACHE_FLASH_ATTR LarpHackableRfidLock::webAdminRFIDPageCallback() { if(Lock.debugStream_ != nullptr) { Lock.debugStream_->println(F("webAdminRFIDPageCallback")); @@ -940,6 +980,7 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::webAdminRFIDSettingsPageSaveButtonP } if(Lock.configuration_changed_ == true) { Lock.rfid_authorisation_enabled_ = Lock.web_admin_rfid_settings_page_check0_->isChecked(); + Lock.web_admin_rfid_settings_page_save_button_->setVisible(false); Lock.save_configuration_soon_ = millis(); } } @@ -1050,6 +1091,7 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::webAdminMQTTSettingsPageSaveButtonP Lock.mqtt_username_ = String(Lock.web_admin_mqtt_settings_page_text3_->value()); Lock.mqtt_password_ = String(Lock.web_admin_mqtt_settings_page_text4_->value()); Lock.mqtt_topic_ = String(Lock.web_admin_mqtt_settings_page_text5_->value()); + Lock.web_admin_mqtt_settings_page_save_button_->setVisible(false); Lock.save_configuration_soon_ = millis(); } } @@ -1151,11 +1193,11 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::webAdminGameSettingsPageSaveButtonP Lock.debugStream_->println(F("webAdminGameSettingsPageSaveButtonPressed saving configuration")); } if(Lock.configuration_changed_ == true) { - Lock.game_enabled_ = Lock.web_admin_game_settings_page_check0_->isChecked(); Lock.game_enabled_ = Lock.web_admin_game_settings_page_check0_->isChecked(); Lock.game_type_ = Lock.web_admin_game_settings_page_radio0_->selectedOption(); Lock.game_length_ = Lock.web_admin_game_settings_page_slider0_->intValue (); Lock.game_retries_ = Lock.web_admin_game_settings_page_slider1_->intValue (); + Lock.web_admin_game_settings_page_save_button_->setVisible(false); Lock.save_configuration_soon_ = millis(); } } @@ -1271,6 +1313,7 @@ void ICACHE_FLASH_ATTR LarpHackableRfidLock::webAdminTwoFactorSettingsPageSaveBu Lock.multi_factor_partner_name_ = String(Lock.web_admin_multi_factor_settings_page_text0_->value()); Lock.multi_factor_partner_type_ = (multiFactorPartnerOption)Lock.web_admin_multi_factor_settings_page_radio1_->selectedOption(); Lock.multi_factor_timeout_ = String(Lock.web_admin_multi_factor_settings_page_text1_->value()).toInt() * 1000; + Lock.web_admin_multi_factor_settings_page_save_button_->setVisible(false); Lock.save_configuration_soon_ = millis(); } }