logo elektroda
logo elektroda
X
logo elektroda

OpenBeken custom driver testing with simulated IO - PIR driver development example

p.kaczmarek2  1 3930 Cool? (+9)
📢 Listen (AI):

TL;DR

  • Builds a custom OpenBeken PIR-controlled LED lamp driver with a light sensor, offering manual and automatic modes for motion-based lighting control.
  • Uses the Windows OBK simulator to map GPIO inputs, expose configuration through the HTTP web interface, and debug the driver with breakpoints.
  • The device template uses pin 6 for PIR data, pin 23 for the light sensor, and pin 26 for PIR sensitivity.
  • Self tests simulate ADC light readings and motion states, then verify LED enable, timeout shutoff, and no-light response when the environment is bright.
  • A deliberate bug in the countdown logic caused tests to fail, showing that GitHub self-tests catch regressions before hardware deployment.
LED floodlight with motion sensor by brennenstuhl.
Here I will show you how I created and tested a PIR-controlled LED lamp (with light sensor) driver for OpenBeken - all without access to physical device. This topic will cover whole development process: from concept, through simulation, to automated testing. Whether you're learning how to contribute to OBK, or just exploring firmware inner workings, this tutorial shows how you can confidently write and verify a custom driver using only OBK simulator and self tests.

You’ll see how to simulate GPIO inputs like motion and ambient light levels, how to expose driver controls via the HTTP web interface, and how to validate correct behavior with unit tests.

Warning: this topic is a development tutorial/guide for OBK and requires some basic C programming knowledge, basic OBK knowledge will also help!

Generic driver idea and used hardware
The following PIR driver will run on a LED lamp that has a motion sensor (boolean value) with controlled sensitivity (via PWM) and a light sensor (ADC). The PIR driver should feature two control modes, first manual, which just ignores all sensors and allows user to control it, and an automatic mode, which is using motion input, ADC input and user-defined settings (time on and light margin) to enable light. Light is only enabled when there is a motion and the environment is dark enough - it should not be enabled during the day.
Sample OBK device template (as sent to me by user who has this device):
Code: JSON
Log in, to see the code

Pins 8 and 9 are used for CW light control, so they can be ignored. What matters to us is pin 6, which is PIR data input, pin 23, which is light sensor input and pin 26, which is used to set PIR sensitivity.

Step 1 - Environment setup
OpenBeken can be run directly on Windows platform with a virtual device simulator. All required dependencies are available for download in simulator repository. Just open the MSVC project (update it if you prefer newer MSVC):
Screenshot of Windows Explorer showing programming project files, including a Visual C++ project file and configuration files.
Then you should be able to both build and debug OBK Windows port:
Screenshot of Microsoft Visual Studio with a C project and source code editor open.
You can put a breakpoint in any function that is included in Windows port and you'll be able to investigate variable values, stack, etc. Of course, it does not include platform-specific code, but that's expected by design.
Screenshot of Visual Studio showing C code file open during debugging, related to command management.
You can also access simulated device page and schematic tool:
Screenshot of the OpenBeken IoT simulator with layout of components.
OpenBeken IoT simulator homepage with device status information and configuration buttons.

Step 2 - Initial driver setup
Now it's time to start writing a driver. I've already covered a basic driver procedure in the separate topic, so I will just link to it below:
[English] How to create a custom driver for OpenBeken with online builds (no toolchain required)
[Polish] How to create a custom driver for OpenBeken with online builds (no toolchain required)
Driver can basically have a quick and slow tick functions (slow tick is called 'on every second', but soon will be customizable), init function, shutdown, and can also receive channel change and HTTP page refresh callbacks, which can be used for custom user interface.

Step 3 - First driver draft
So, as shown in previous tutorial, I've started with drv_main.c entry:
Code: C / C++
Log in, to see the code

Then I've prepared the function stubs:
Code: C / C++
Log in, to see the code

Init and OnEverySecond functions should be obvious, one is called on start, and second periodically - on every second. The HTTP page function is more interesting - it provides access to static HTML (if bPreState is 1), and to the dynamic HTML state which is refreshed every second (if bPreState is 0). The static html should be used for configuration page with user inputs, and dynamic section can be used to display current driver status information.
Code: C / C++
Log in, to see the code

I've also included function headers, including the flash vars for state read and write.
So, let's start with the init function. In this case, we need this function to automatically find matching channels for PIR device and also restore settings from flashvars:
Code: C / C++
Log in, to see the code

This driver assumes that user has set the required pin roles or/and channel types and it uses just first channel/pin that matches the requirement. It should be good enough for the start, more customizable version can be done later.

Then let's consider HTML page handler, which is used to create configuration UI and display current PIR state:
Code: C / C++
Log in, to see the code

This callback is both creating the HTML page and receiving the data from submitted HTML form via http_getArg function. Parameters are later saved to flash vars, so they are persistent between reboots.

Finally, I've implemented the PIR logic, which is refreshed every second:
Code: C / C++
Log in, to see the code

The logic above is fairly simple. In automatic mode, driver is first testing light level in the environment. Then the PIR is checked. If the environment is light and there is a motion, then light is enabled and timer is set to initial value. Then timer is ticked and if it runs out, the LED is disabled. That's enough for basic PIR functionality.

Step 4 - Testing driver configuration manually
This is a somewhat obvious step, but it's still worth to mention it. While developing a driver, it's very easy to test it directly on Windows. You just need to run the simulator and access the OBK page on your PC IP, by default using port 80. Port can be changed via command line, if you want to run multiple OBK devices at the same time.
That way I can get this:
PIR sensor settings panel with sliders and control buttons on a dark background.
I can put breakpoints in the driver code to see what happens:
Screenshot of Visual Studio editor showing C source code.
I can set states on simulated GPIOs by shorting them to ground or power (or by using a button):
Electronic circuit diagram in OpenBeken Simulator with a WB3S microprocessor and instructions on the right.
(of course, the schematic is simplified, you should not be attempting to create such circuit in reality).
This may be helpful for testing and playing around, especially since Simulator also supports Home Assistant, but there is still even better solution...

Step 5 - Adding self tests while developing
OpenBeken automatic testing was briefly described here. This mechanism resides in src/selftest directory. For this example, let's consider selftest_pir.c code which we can later call from the main test caller:
Code: C / C++
Log in, to see the code

First, the code above is setting required GPIO and channels configuration and then it starts the driver. Once driver is running, some basic configuration to PIR is done via simulated HTTP packets - this is the same what user browser sends when the HTML form is submit:
Code: C / C++
Log in, to see the code

Finally, the IO state is simulated as well - both ADC (light level) reading and motion state (boolean). Then initial behaviour of PIR driver is tested.
Code: C / C++
Log in, to see the code

PIR driver should enable LED as soon as motion is detected and there is not enough environmental lighting. So, LED_GetEnableAll should return 1. Following test will cause assertion if LED is not behaving as expected.
Then, we simulate continous motion:
Code: C / C++
Log in, to see the code

Just to be sure - before motion goes away:
Code: C / C++
Log in, to see the code

Then, after a simulated wait longer than LED on time, the LED should turn off. This is tested now as well.
Finally, we're testing one more scenario - what if there is a motion, but environment is bright enough that we don't need LED?
Code: C / C++
Log in, to see the code

Light should turn off in this case, and yes, it does as expected.


What would happen if there was an error in a driver?
Now, for the sake of the presentation, let's break our driver logic. I've moved the countdown else inside the "isDark" block, so now time goes down only if there is no light:
Screenshot showing code differences in drv_pir.c; removed lines are marked in red, added lines in green.
This makes driver behaviour incorrect - the LED will not turn off if it's on when the day starts.
Now I run the self tests, and yes, they have detected the problem;
A console window running the OpenBeken program, displaying operation logs and debugging information.
The assertion fails and developer immediatelly knows that something bad happened to the driver. Don't forget the same tests are ran on Github, so even external commits (pull requests) are checked as well.

Summing up, this is how I can develop drivers for OBK quickly and easily. Thanks to Windows port/simulator mechanism, I can:
- quickly run and test features directly on PC
- simulate simple circuits and GPIO states with "schematic editor" tool
- debug OBK behaviour in effective MSVC debugger (put breakpoints, check variable values, inspect memory)
- automatically test OBK behaviour on each commit/run with self tests
- connect OBK to HA without having physical device (Simulator supports MQTT via LWIP)
Of course, this approach is not a full replacement to testing on physical device, as some code is per-platform and device-dependent, but it still provides a great starting point for rapid development and validation of OBK drivers, at least when they are not strongly focused on per-platform mechanism like interrupts or DMA...
PS: PIR driver was already tested by our user on physical device and it's reported to work good, altough probably I will still improve it more in the upcoming days

About Author
p.kaczmarek2
p.kaczmarek2 wrote 14219 posts with rating 12110 , helped 647 times. Been with us since 2014 year.

Comments

b_benz38 30 May 2025 21:53

#if ENABLE_DRIVER_PIR    //drvdetail:{"name":"PIR",    //drvdetail:"title":"TODO",    //drvdetail:"descr":"PIR",    //drvdetail:"requires":""}    { "PIR",      PIR_Init,         PIR_OnEverySecond... [Read more]

FAQ

TL;DR: In 15 Q&A pairs, this guide shows how to build and verify an OpenBeken PIR lamp driver on Windows without hardware. The author’s verdict is simple: "quickly and easily." It covers simulator setup, GPIO and ADC emulation, an HTTP config page, and self-tests that catch timer bugs before real-device checks. [#21551399]

Why it matters: This FAQ helps OpenBeken contributors and firmware tinkerers validate custom PIR automation logic before they ever touch the physical lamp.

Method What you can validate Main tools Main limit
OpenBeken simulator on Windows Driver logic, HTTP UI, GPIO/ADC behavior, self-tests MSVC, web UI, schematic editor, simulated HTTP No platform-specific hardware behavior
Real device testing Final behavior on actual lamp hardware Physical PIR lamp, real sensors Slower iteration and harder debugging

Key insight: The fastest OpenBeken driver workflow is simulator-first, hardware-second. You can prove most PIR logic with simulated inputs and automated tests, then confirm only device-specific behavior on the real lamp.

Quick Facts

  • The sample PIR lamp template uses pin 6 for motion input, pin 23 for the ADC light sensor, and pin 26 for PWM-based PIR sensitivity control. [#21551399]
  • The driver loop runs on an every-1-second callback, and the example self-test sets the lamp on-time to 5 seconds. [#21551399]
  • The HTTP settings page exposes four persistent controls: mode, on-time, sensitivity, and light margin; the sensitivity slider spans 1-100. [#21551399]
  • The Windows simulator serves the device page on the PC IP and uses port 80 by default, which supports fast browser-based checks during development. [#21551399]
  • The self-test simulates a dark room with an ADC value of 3000 and a bright room with 500, then verifies that the LED turns off after a 7-second wait. [#21551399]

How do I create and test a custom PIR-controlled LED lamp driver in OpenBeken without having the physical device?

You can build the driver in OpenBeken’s Windows simulator, then verify it with simulated inputs and self-tests. 1. Add a custom driver entry and implement functions such as PIR_Init and PIR_OnEverySecond. 2. Map the needed pins, start the driver, and expose settings through the HTTP page. 3. Simulate motion and ADC light values, then assert LED behavior with automated tests. This workflow covers logic, UI, and timing before real hardware is available. [#21551399]

What is the OpenBeken virtual device simulator, and how does it help with driver development on Windows?

The OpenBeken virtual device simulator is a Windows-based development environment that runs the firmware without the target lamp. "Virtual device simulator" is a software tool that emulates an OpenBeken device, exposes its web interface, and lets you test GPIO logic, HTTP handling, and driver behavior without physical hardware. It lets you build and debug in MSVC, open the simulated device page, and inspect variables with breakpoints. That shortens iteration time and makes driver bugs easier to reproduce. [#21551399]

How can I simulate PIR motion input and ADC light sensor values in the OpenBeken simulator?

You simulate motion by changing a digital input state and simulate ambient light by writing an ADC value. The example uses SIM_SetSimulatedPinValue(6, true/false) for PIR motion and SIM_SetIntegerValueADCPin(23, value) for the light sensor. A dark test case uses an ADC reading of 3000, while a bright case uses 500. After setting those values, run time forward with Sim_RunSeconds(...) and check whether the LED state matches the expected PIR logic. [#21551399]

Which GPIO roles and channel types should I use in OpenBeken for a PIR lamp with motion input, light sensor ADC, and PWM sensitivity control?

Use a digital input for motion, an ADC for light level, and PWM_ScriptOnly for PIR sensitivity. In the sample template, pin 6 is dInput on channel 11, pin 23 is ADC on channel 12, and pin 26 is PWM_ScriptOnly on channel 10. The driver first looks for a motion channel type, then falls back to matching digital input pin roles if needed. That makes the first usable motion input enough for a basic implementation. [#21551399]

How do I add a custom driver entry and function stubs like PIR_Init and PIR_OnEverySecond in OpenBeken?

Add a driver record in drv_main.c, then implement the callback stubs in your driver source file. The example registers { "PIR", PIR_Init, PIR_OnEverySecond, PIR_AppendInformationToHTTPIndexPage, NULL, NULL, PIR_OnChannelChanged, false } under #if ENABLE_DRIVER_PIR. It then defines four functions: PIR_Init, PIR_OnEverySecond, PIR_OnChannelChanged, and PIR_AppendInformationToHTTPIndexPage. That gives you startup, periodic logic, channel-change handling, and HTTP UI hooks in one place. [#21551399]

What is the purpose of flash vars in OpenBeken, and how are they used to save PIR settings between reboots?

Flash vars store persistent driver settings so the PIR configuration survives a reboot. "Flash vars" are persistent firmware variables that save channel-like values in flash memory, survive power loss, and let a custom driver restore settings such as mode, sensitivity, timer length, and light threshold during initialization. The example reads them in PIR_Init with HAL_FlashVars_GetChannelValue(...) and writes them after HTTP form changes with HAL_FlashVars_SaveChannel(...). It stores on-time, sensitivity, mode, and light margin in four reserved variable slots. [#21551399]

How can I build a custom HTTP configuration page in OpenBeken to control PIR mode, on-time, sensitivity, and light threshold?

Build it inside PIR_AppendInformationToHTTPIndexPage by handling both form input and HTML output. When bPreState is 1, the driver reads GET parameters like pirTime, pirSensitivity, pirMode, and light, then saves them to flash vars. It also renders a form with text inputs, a 1-100 range slider, and two radio buttons for manual or auto mode. When bPreState is 0, it prints live state, including motion, darkness status, and remaining time. [#21551399]

Why does the sample OpenBeken PIR driver treat higher ADC readings as darker ambient conditions, and how should the light margin be set?

It does that because the tested lamp’s sensor reports higher ADC values when the environment is darker. The author states that the value “go[es] down if MORE light is here and UP is LESS light is here,” so the driver sets g_isDark when lightLevel > g_lightLevelMargin. In the self-test, a margin of 2000 makes 3000 count as dark and 500 count as bright. Set the margin around the sensor’s real crossover point on your lamp, then verify it with simulated ADC values. [#21551399]

What is the difference between manual mode and automatic PIR mode in the OpenBeken PIR driver example?

Manual mode ignores the sensors, while automatic mode uses motion, light level, and user settings to control the LED. In manual mode, the driver only resets g_timeLeft to 0 and does not run PIR-based switching. In automatic mode, it checks whether the room is dark enough and whether motion is present, then turns the LED on and starts or refreshes the timer. The HTTP page labels those choices as “Manual Mode” and “AUTO Mode with PIR.” [#21551399]

How should the countdown timer logic work in an OpenBeken PIR driver so the LED turns off correctly after motion stops or daylight appears?

The countdown should continue whenever g_timeLeft > 0, not only while the room stays dark. In the working example, motion in dark conditions sets g_timeLeft = g_onTime and enables the LED. Each 1-second tick decrements the timer, and when it reaches 0, the driver calls LED_SetEnableAll(false). That logic lets the lamp turn off after motion ends and also lets an already-lit lamp shut down correctly if daylight appears before the timer expires. [#21551399]

How do I write self tests for an OpenBeken custom driver using SIM_ClearOBK, Test_FakeHTTPClientPacket_GET, and SELFTEST_ASSERT?

Write the test by resetting the simulator, configuring pins, sending fake HTTP settings, and asserting output states. 1. Call SIM_ClearOBK(0), format storage, assign pin roles, and start the driver. 2. Use Test_FakeHTTPClientPacket_GET(...) to set values like pirMode=1, pirTime=5, and light=2000. 3. Simulate ADC and motion changes, run time with Sim_RunSeconds(...), and verify results with SELFTEST_ASSERT(...). This method tests both configuration handling and runtime behavior in one repeatable flow. [#21551399]

Why would an OpenBeken self test catch a PIR driver bug where the LED stays on after the environment becomes bright?

It catches that bug because the test explicitly switches the simulated room from dark to bright while the LED is already on. The broken version moved the countdown path inside the isDark block, so the timer stopped decreasing once daylight appeared. The self-test then set the ADC to 500, waited 7 seconds, and expected the LED to be off. The assertion failed, which immediately exposed the logic error and showed that the LED could stay on indefinitely in bright conditions. [#21551399]

OpenBeken simulator vs testing on a real device: which parts of custom driver validation can be trusted in each approach?

Trust the simulator for logic, timing, UI, and repeatable regression tests, and trust the real device for hardware-specific behavior. The simulator validates GPIO state changes, ADC decisions, HTTP pages, breakpoints, and self-tests on Windows. It can even connect through MQTT and Home Assistant without the physical lamp. The author also states that simulator work is not a full replacement for real-device checks because platform-specific and device-dependent code still needs hardware confirmation. [#21551399]

What is the schematic editor in the OpenBeken simulator, and how can it be used to emulate GPIO shorting, buttons, and simple test circuits?

The schematic editor is the simulator tool for interactive GPIO experiments. It lets you emulate simple circuits by shorting simulated pins to ground or power and by adding a button-like control. That makes it useful for quick manual checks before you write or update self-tests. The author warns that the schematic is simplified, so you should not copy it as a real hardware design, but it works well for software-side input emulation. [#21551399]

How can I debug an OpenBeken custom driver in MSVC on Windows using breakpoints, variable inspection, and the simulated web interface?

Build the Windows port in MSVC, place breakpoints in driver code, and use the browser-based device page to trigger your logic. The simulator repository provides the required dependencies, and the author shows that you can debug included Windows-port code directly. You can inspect variables, the call stack, and memory while changing driver settings through the simulated HTTP interface on the PC IP, typically on port 80. This approach gives fast, source-level debugging without flashing hardware between changes. [#21551399]
%}