An IoT lockbox for hybrid scavenger hunts.
Cipherlock is an IoT-based lockbox for hybrid scavenger hunts.
Cipherlock consists of three main components: the lockbox, the lockbox controller, and the game server. In a future version, a fourth key component will the LoRa gateway.
The lockbox is a 3D-printed treasure chest that cannot be manually opened from the outside.
Internally, the lockbox uses a spring-loaded latch to provide to hold the lid shut. The lockbox controller controls a servo motor which retracts that lid, allowing the box to open.
Magnets in the box's lid and base ensure that the lid opens automatically, once the latch is retracted.
The lockbox controller unlocks the box when it receives a command to do so from the game server. In its current implementation, the controller connects directly to the game server via a WebSocket. In a future version, this will be relayed through a LoRa connection between the lockbox controller and a LoRa gateway.
Outward, the game server provides REST API with several endpoints, one of which being POST /checkAnswer
. A game spec file defines what type of answer is expected for a given lockbox (a.k.a. cache) and what answers are considered correct. When an acceptable answer is provided during a requet to the /checkAnswer
endpoint, the corresponding lockbox receives an unlock command, causing the lid to open automatically. A single game spec may define an arbitrary number of caches, each with their corresponding lockbox and answer definition.
This repository contains the code for the following components of Cipherlock:
game-server
: The web API that contains the core game logic, such as receiving player answers and triggering lockboxes to unlock.lockbox
: The ESP32-based lockbox controller.lora-gateway
: Not yet implemented.
- CAD files (not currently available to the public)
- Player and admin frontends (not part of this project; see Implementing a user interface)
- Basic ESP32 development board setup (see Flashing the Micropython firmware)
When setting up Cipherlock, it is recommended that you first complete the external tasks outlined below, before proceeding with the deployment of Cipherlock itself.
This project requires the Micropython firmware to be installed on all ESP32 devices.
Clone this repository and follow the instructions outlined in the README to flash the appropriate Micropython firmware onto the ESP32 device. There, you can also find general information on how to interact with an ESP32 device through a serial connection (REPL and file transfer).
Player and admin UI are not part of this project. They are developed individually and use the provided endpoints. For an example, see here.
Print all the required parts and assemble the lockbox. For instructions, see here.
Run
yarn run start
to start the game server in development mode.
If ENV=dev
is set, an example game will be loaded automatically upon startup. Else, perform a request to load a game spec next.
To deploy the application, you can either push the game server directory to a Dokku server or run
yarn run build
to create a deployment build in dist
. Then, run
node dist/server.js
to launch the application.
API_KEY
: server's API key (required for authentication on/admin
endpoints)PORT
: port on which to serve the applicationENV
: if set todev
, an example game is loaded automatically upon startup
Unless stated otherwise, all commands within this section need to be run from within the lockbox
subdirectory.
If you haven't deployed to a lockbox controller from this machine (or this repository clone) before, first make sure to create and activate a virtualenv and run pip install -f requirements.txt
from within the root of this repository.
Then, from within the lockbox
subdirectory, run
cp config.example.py config.py
and edit the newly created config.py
file to fit your specific configuration. This file should never be checked into source control!
Finally, run
chmod +x deploy.sh
to complete the first-time setup.
The following steps are ideally done immediately after a fresh firmware flash (see flashing the Micropython firmware).
Connect the target ESP32 to your computer and run
./esptool.py chip_id | grep "Serial port"
to determine its serial port.
Then, run
./deploy.sh <serial_port>
to deploy all files to the device.
To set up additional lockbox controllers, you will need to edit the DEVICE_ID
value in config.py
before each deployment. Each lockbox needs to have an individual numeric id between 1
and 255
.
Once the configuration has been updated for the next deployment, follow the same steps as when Deploying to the first lockbox controller.
When updating individual files (such as config.py
) on a previously deployed lockbox controller, run
./deploy <serial_port> <filename>
to copy only the specified file to the device.
The LoRa gateway is not yet implemented. Currently, the lockbox controller connects directly to the game server via WiFi.
See src/models.ts
for request / response models.
Endpoint: POST /admin/game
Body: GameSpec
Headers:
apiKey
: server's API key
Returns:
204 NO CONTENT
: game loaded401 UNAUTHORIZED
:apiKey
header missing or invalid400 BAD REQUEST
: payload notapplication/json
Required for players before checkAnswer
requests if requireKnownPlayers
is set to true
in game spec.
Endpoint: POST /onboard
Body: OnboardingRequest
Returns:
200 SUCCESS
: onboarding successful → OnboardingSuccessResponse409 CONFLICT
: no game active, invalid gameId, player name invalid or unavailable →OnboardingErrorResponse
Endpoint: POST /checkIn
Body: CheckInRequest
Returns:
200 SUCCESS
:CheckInResponse
409 CONFLICT
: no game active
Endpoint: POST /checkAnswer
Body: AnswerCheckRequest
Returns:
200 OK
: valid request →AnswerCheckResult
409 CONFLICT
: no game active, invalid gameId, playerId or questionId →AnswerCheckErrorResponse
Please note that the CAD files for this project are not currently available to the public.
Aside from the 3D-printed components, the following parts and tools are required:
- 1x LoRa-capable ESP32 development board (e.g. this SX1276 ESP32 LoRa Development Board)
- 1x SG90 9g servo motor
- 4x 8x2mm magnets
- 1x 6.5x10mm compression spring (6.5mm outer diameter, ~0.5mm wire diameter)
- 4x M3x10 pan head screws
- 4x M3x16 pan head screws
- 8x M3 hex nuts
- Soldering iron
- Hot glue gun
- Start by assembling the circuitry. Solder wires to the
5V
(red) andGND
(black) pins, as well as any third pin (orange) on the ESP32 development board. This third pin will be theSERVO_PIN
and must be set accordingly inconfig.py
. To avoid damaging the LoRa transceiver, make sure to also connect the LoRa antenna even if it is not yet used in this project. - It is now recommended to perform a first smoke test: Connect a servo motor to the wires, install the Cipherlock firmware on the microcontroller and trigger an unlock by sending a "correct"
/checkAnswer
request to the game server. - Mount the servo motor to the front panel using two M3x16 screws and corresponding nuts. Rotate the servo to its rightmost position, and attach the lever arm pointing to the right as shown below, to allow for a full 180° rotation.
- Apply a small amount of hot glue to mount an 8x2mm magnet in each of the sockets on the lid.
- Use a small amount of hot glue to mount an 8x2mm magnet in each of the sockets on the base. These magnets must have opposite polarity to those on the lid, so they repel each other. Then, use a soldering iron to sink an M3 hex nut into each six front-facing sockets on the base (four on the front column and two on the rear panel). Note: All nut sockets are intentionally cut smaller than the outer diameter of the hex nut.
- Assemble the base, lid and latches as shown below, using two M3x16 screws. Due to the magnets, the lid should now come to rest at a slight angle.
- Use a soldering iron to sink an M3 nut into the corresponding socket on the hinge. Careful: The two sides of the hinge have different cutouts. Be sure to use the side facing the servo during this step.
- Fasten the latch to the front panel as shown below using an M3x16 screw and a 6.5x10mm compression spring.
- Connect the servo and a USB cable to the ESP32 microcontroller and place the assembly into the box. Use the cutout at the rear of the base to route the USB cable into the box. At this point, you should ideally perform another smoke test before proceeding to the next step.
- Lower the cover into the base, ensuring no pressure is applied to any components or solder joints. Before proceeding to the next step, rotate the servo motor to the left until it touches the latch (not shown in the image).
- For the final step, fasten the front panel to the base using four M3x10 screws.
The box is now fully assembled. To complete the setup, the following next steps are recommended in this order:
- If not done already, deploy the lockbox controller firmware to the ESP32 device.
- Perform a smoke test by triggering an unlock via a "correct"
/checkAnswer
request to the game server. The latch should now retract and quickly be released back into its neutral position. If the latch does not get released (i.e. the servo does not return to idle), immediately disconnect the USB cable to avoid damage. Change theIDLE_ANGLE
andUNLOCK_ANGLE
properties inconfig.py
accordingly (seeconfig.example.py
for further information). - Close the lid check whether the box properly locks.
- Perform another unlock and verify that the box opens as expected.