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.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 to dev
, an example game is loaded automatically upon startupUnless 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 keyReturns:
204 NO CONTENT
: game loaded 401 UNAUTHORIZED
: apiKey
header missing or invalid400 BAD REQUEST
: payload not application/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 activeEndpoint: 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:
5V
(red) and GND
(black) pins, as well as any third pin (orange) on the ESP32 development board. This third pin will be the SERVO_PIN
and must be set accordingly in config.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.
/checkAnswer
request to the game server.The box is now fully assembled. To complete the setup, the following next steps are recommended in this order:
/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 the IDLE_ANGLE
and UNLOCK_ANGLE
properties in config.py
accordingly (see config.example.py
for further information).