[{"data":1,"prerenderedAt":388},["ShallowReactive",2],{"project-case-someday":3},[4,13,19,51,67,84,143,316,348,375,381],{"component":5,"data":6},"ProjectCaseHeader",{"index":7,"subtitle":8,"name":9,"headline":10,"url":11,"tone":12},"08","DRIVER LIBRARIES · ELECTRONICS FOR A ROLE-PLAYING GAME","Someday","11 driver libraries that powered the devices at the 2026 game",null,"accent",{"component":14,"data":15},"ProjectCaseCover",{"image":16,"imageMobile":17,"imageAlt":18},"someday\u002Fmedia\u002Fpreview.svg","someday\u002Fmedia\u002Fpreview-mobile.svg","Illustration: an ESP32 board wired to RFID, GPS, display and LED strip modules",{"component":20,"data":21},"ProjectCaseMeta",{"tags":22,"stack":23},[],[24,27,29,31,33,35,37,39,41,43,45,47,49],{"label":25,"own":26},"C++",true,{"label":28,"own":26},"Arduino",{"label":30,"own":26},"ESP32",{"label":32,"own":26},"FreeRTOS",{"label":34,"own":26},"PlatformIO",{"label":36,"own":26},"MFRC522",{"label":38,"own":26},"PN532",{"label":40,"own":26},"NDEF",{"label":42,"own":26},"TinyGPSPlus",{"label":44,"own":26},"ArduinoJson",{"label":46,"own":26},"FastLED",{"label":48,"own":26},"U8g2",{"label":50,"own":26},"OneButton",{"component":52,"data":53},"ProjectCaseFacts",{"facts":54},[55,58,61,64],{"key":56,"value":57},"role","All the software, libraries author",{"key":59,"value":60},"team","3 people: software, hardware, enclosures",{"key":62,"value":63},"duration","Since January 2025, libraries since October 2025",{"key":65,"value":66},"status","In development, field-tested at the 2026 game",{"component":68,"data":69},"ProjectCaseRole",{"id":56,"name":70,"items":71},"What I did",[72,75,78,81],{"title":73,"text":74},"The someday-lib core","FreeRTOS tasks, thread-safe state, serializable game effects and RFID on RC522 for Mifare Classic and Ultralight cards.",{"title":76,"text":77},"Module drivers","Buttons, LEDs, an addressable LED strip, the battery, a display with Cyrillic, Wi-Fi, GPS, RFID on PN532 and a Serial console with commands.",{"title":79,"text":80},"Library architecture","Shared modules versioned with git tags instead of copied code in every firmware. Arduino and PlatformIO were my choice as well.",{"title":82,"text":83},"Rules for the AI assistant","Claude Code helps me make sense of C++ and writes separate pieces. For it I made the someday-guard plugin with hooks and rules: a driver template is created quickly, and I review and finish it myself.",{"component":85,"data":86},"ProjectCaseChapter",{"id":87,"name":88,"blocks":89},"context","Context and starting point",[90,102,110,135],{"component":85,"data":91},{"id":92,"name":93,"blocks":94},"business","The project",[95,99],{"component":96,"data":97},"ProjectCaseText",{"text":98},"KB Someday is a team of enthusiasts building electronics for a S.T.A.L.K.E.R. field role-playing game in the Urals. The project is non-commercial and has no external client: tasks come from the game idea of the location.",{"component":96,"data":100},{"text":101},"There are three of us: I write all the software, the second member handles the hardware (circuits, soldering, assembly), and the third makes the enclosures. The libraries are used by the firmware of the game devices, and the devices are used by players on the field.",{"component":85,"data":103},{"id":104,"name":105,"blocks":106},"situation","Situation",[107],{"component":96,"data":108},{"text":109},"At first every device was written from scratch, and code repeated from firmware to firmware: buttons, LEDs, RFID, communication. I moved the shared code into the someday-* libraries, and a new device started coming together from ready-made modules faster.",{"component":85,"data":111},{"id":112,"name":113,"blocks":114},"constraints","Field conditions",[115],{"component":116,"data":117},"ProjectCaseArchitecture",{"items":118},[119,123,127,131],{"label":120,"title":121,"text":122},"POWER","Battery","A device has to run for several days on a battery, so we save power: sleep, turning off the backlight.",{"label":124,"title":125,"text":126},"OFFLINE","No internet","There is no internet and no stable Wi-Fi on the field. Devices work on their own or talk to each other directly.",{"label":128,"title":129,"text":130},"FIELD","Outdoors and players","Debouncing, protection against careless handling, fast recovery after a failure.",{"label":132,"title":133,"text":134},"BUDGET","Small budget","Cheap modules, ESP32 and Chinese sensors.",{"component":85,"data":136},{"id":137,"name":138,"blocks":139},"goal","Goal",[140],{"component":96,"data":141},{"text":142},"Make the role-playing game more varied with our own devices and build every new device from ready-made modules instead of writing it from scratch.",{"component":85,"data":144},{"id":145,"name":146,"blocks":147},"process","Process, architecture and decisions",[148,189,225,255,266,286],{"component":85,"data":149},{"id":150,"name":151,"blocks":152},"architecture","Architecture",[153,156],{"component":96,"data":154},{"text":155},"The libraries split into a core and module drivers. They all follow the same rules, so modules from different libraries live together in one firmware without extra work.",{"component":116,"data":157},{"items":158},[159,167,181],{"label":160,"title":73,"text":161,"code":162},"CORE","FreeRTOS tasks on CRTP, a value with a lifetime behind a mutex, serializable game effects: radiation and psi healing, a timer, death, revival and reset. RFID on RC522 with strategies for Mifare Classic and Ultralight.",[163,164,165,166],"Task\u003CDerived>","Singleton","GuardedState\u003CT>","EffectData",{"label":168,"title":76,"text":169,"code":170},"DRIVERS","Several buttons in one firmware, an LED, a WS2812B addressable strip with the pin as a template parameter, a battery with a mock mode, a thread-safe 128×64 display with Cyrillic and PWM backlight, Wi-Fi in access point and client modes, GPS, RFID on PN532, a Serial console with commands, BLE artifact types.",[171,172,173,174,175,176,177,178,179,180],"someday-button","someday-diod","someday-address-led","someday-accumulator","someday-display","someday-wifi","someday-gps","Someday_RFID","Someday_SerialCommander","someday-types",{"label":182,"title":183,"text":184,"code":185},"CONVENTIONS","Shared rules","Every module has its own namespace. Modules are guarded by mutexes and never block: process() is called from the loop or from a task. Built through library.json for PlatformIO, versions on git tags.",[186,187,188],"someday::\u003Cmodule>","process()","library.json",{"component":85,"data":190},{"id":191,"name":192,"blocks":193},"devices","What the devices are built from",[194,197],{"component":96,"data":195},{"text":196},"The firmware of six game devices runs on the libraries: a dosimeter, a scanner, a geo-tagger, a PKS, a BLE artifact with a detector and a master device for RFID cards. Below is what each firmware pulls in.",{"component":116,"data":198},{"items":199},[200,208,212,216,219],{"label":201,"title":202,"code":203},"SOMEDAY-LIB","Core",[204,205,206,207],"device-uyut","device-scanner","device-candle","rfid-tool",{"label":209,"title":210,"code":211},"MODULES","GPS, Wi-Fi, buttons, light, Serial console",[204,205,206],{"label":213,"title":214,"code":215},"DISPLAY · POWER","Display and battery",[204,206],{"label":217,"title":178,"code":218},"RFID",[204,207],{"label":220,"title":221,"code":222},"SOMEDAY-TYPES","BLE artifact types",[223,224],"device-artefact","device-otklick",{"component":85,"data":226},{"id":227,"name":228,"blocks":229},"hardware","Hardware and buses",[230],{"component":116,"data":231},{"items":232},[233,237,240,244,248,252],{"label":234,"title":235,"text":236},"MCU","ESP32-S3 and ESP32-C3","Including ESP32-S3 N16R8 with PSRAM.",{"label":217,"title":238,"text":239},"RC522 and PN532","RC522 over SPI, PN532 over UART.",{"label":241,"title":242,"text":243},"GPS","AT6558 \u002F GP-02","UART at 9600 baud, NMEA and CASIC commands.",{"label":245,"title":246,"text":247},"DISPLAY","JLX12864G-086","128×64 on the ST7567 controller, software SPI, PWM backlight.",{"label":249,"title":250,"text":251},"LIGHT","WS2812B and LEDs","The addressable strip through FastLED, buttons through OneButton.",{"label":120,"title":253,"text":254},"Two 18650 batteries","Voltage is read through a divider on the ADC: 1250–2100 mV map to 0–100 %.",{"component":85,"data":256},{"id":257,"name":258,"blocks":259},"stack","Why Arduino and PlatformIO",[260,263],{"component":96,"data":261},{"text":262},"The project was my way into embedded; before that I worked on the web. Arduino and PlatformIO give a fast start and plenty of ready-made libraries: for an embedded newcomer it is the fastest path.",{"component":96,"data":264},{"text":265},"Along the way I learned C++ for microcontrollers, ESP32, FreeRTOS, buses and datasheets. Claude Code helped me make sense of C++.",{"component":85,"data":267},{"id":268,"name":269,"blocks":270},"integrations","Integrations",[271,283],{"component":272,"data":273},"ProjectCaseIntegrations",{"items":274},[275,276,278,279,280,281,282],{"label":36,"own":26,"term":26},{"label":277,"own":26,"term":26},"PN532 + NDEF",{"label":42,"own":26,"term":26},{"label":44,"own":26,"term":26},{"label":46,"own":26,"term":26},{"label":48,"own":26,"term":26},{"label":50,"own":26,"term":26},{"component":96,"data":284},{"text":285},"Third-party libraries talk to the hardware directly, and someday-* wrap them into modules with shared rules.",{"component":85,"data":287},{"id":288,"name":289,"blocks":290},"quality","Quality and releases",[291,294,297,310,313],{"component":96,"data":292},{"text":293},"For someday-lib I wrote Unity unit tests, about 1800 lines, which run right on the device. MockGps and the battery mock mode help to debug without hardware.",{"component":96,"data":295},{"text":296},"Everything else was tested by hand, in three stages.",{"component":298,"data":299},"ProjectCaseSteps",{"steps":300},[301,304,307],{"title":302,"text":303},"Bench","We assemble the device and run scenarios through the SerialCommander Serial console and mocks",{"title":305,"text":306},"Field test","A check on location before the game",{"title":308,"text":309},"Game","The final check at the 2026 game",{"component":96,"data":311},{"text":312},"The libraries are versioned with git tags: someday-gps v2.1.0, Someday_RFID v1.1.0, the rest v1.0.x. Devices pin a version by tag or commit, so a new library version does not change firmware that is already built.",{"component":96,"data":314},{"text":315},"Every library has a README in Russian, and RFID and SerialCommander also have one in English. The someday-gps README runs to 670 lines.",{"component":85,"data":317},{"id":318,"name":319,"blocks":320},"challenges","Challenges",[321],{"component":322,"data":323},"ProjectCaseChallenges",{"items":324},[325,338],{"id":326,"name":327,"rows":328},"rfid-bus","One RFID bus for several tasks",[329,332,335],{"key":330,"text":331},"problem","Several FreeRTOS tasks worked with the PN532 reader over one UART, and the ACK responses got mixed up.",{"key":333,"text":334},"solution","I put one mutex on the whole Someday_RFID driver. Player data is written to the card as an NDEF record through the templated write\u003CT> and read\u003CT>.",{"key":336,"text":337},"result","We found the problem on the bench while debugging, before the game. At the game RFID worked reliably.",{"id":339,"name":340,"rows":341},"typed-rfid","Type-safe writes to the card",[342,344,346],{"key":330,"text":343},"Game data is written to the players' RFID cards, and the firmware must not write anything to a card that cannot be read back.",{"key":333,"text":345},"In someday-lib, readData\u003CT> and writeData\u003CT> accept only serializable POD types through SFINAE and static_assert. After a write the data is verified by reading it back.",{"key":336,"text":347},"An unsuitable type will not compile: the error shows up at build time, not on the field.",{"component":85,"data":349},{"id":350,"name":351,"blocks":352},"results","Metrics and results",[353,369,372],{"component":354,"data":355},"ProjectCaseResults",{"stats":356},[357,360,363,366],{"value":358,"label":359},"11","someday-* driver libraries",{"value":361,"label":362},"6","game device firmwares built on them",{"value":364,"label":365},"18–20 h","of device runtime on two 18650s with no optimizations",{"value":367,"label":368},"~30","players went through the location's quests",{"component":96,"data":370},{"text":371},"At the 2026 game the team played as the Clear Sky faction: a game-tech location with a set of devices built on these libraries, 2–3 units of each. A chain of quests led players through working with different devices, and players spoke well of both the location and the devices.",{"component":96,"data":373},{"text":374},"I came to embedded from the web and wrote driver libraries that devices ran on at a real game. Today I would study the theory more closely at the start and maybe pick C or Rust instead of Arduino: Arduino has a number of serious limitations when working with controllers, and rewriting a finished ecosystem is now expensive.",{"component":376,"data":377},"ProjectCaseCta",{"id":378,"title":379,"text":380,"url":11},"cta","Need firmware for an ESP32 device or drivers for your own hardware?","I will build C++ and FreeRTOS firmware from modules: RFID, GPS, display, LEDs, Wi-Fi.",{"component":382,"data":383},"ProjectCaseNext",{"slug":384,"name":385,"index":386,"tone":387},"qwixit","Qwixit","01","primary",1792947429843]