I've put the current state of the firmware on on Github at https://github.com/kevinkessler/sdl-nrf51. The same firmware is loaded on any type of device, and jumpers on the GPIOs P00-P02 will select the device type. I only have 2 device types now; the SWITCH and POWER_WITH_IP, which is the interface with the Sonoff device. I plan to also support a momentary contact BUTTON (to work as a power toggle, or a master power off), and a POWER relay that doesn't use the Sonoff and its IP connectivity. Reading the GPIOs occur in sdl_config.c and the results are used to initialize the device either in sdl_switch.c or sdl_power_with_ip.c.
The connectivity between all device is handled by the nRF OpenMesh project from Nordic Semiconductor. The mesh software uses Bluetooth LE advertisements to synchronize mesh values of up to 23 bytes in length. There can be up to 65535 values addressed through an unsigned integer called the value handle. Currently the SWITCH firmware write a 0 or 1 to the mesh value, and the POWER_WITH_IP Sonoff gets the mesh value change event and switches the Sonoff power by sending it a uart serial character. This will probably become more complicated as I code in support for 3 way switching and other functionality.
The sdl_service.c file creates a Bluetooth LE service with a characteristic used to set the value handle the SWITCH and POWER_WITH_IP will use to communicate. Nordics nRF Connect software, available here, is used to poke the value handles into the configuration. nRF Connect is free, and well worth playing around with just to get a feel for how BLE works. The value handle configured in the SWITCH and the POWER_WITH_IP must match in order for them to work together, and configuring which switches and power relays talk to each other will just be a matter of having that group of devices listening to the same value handle. The configured value handles are saved to flash in the sdl_config.c file.
I learned two important things when writing this code:
The Nordic SDK is very event driven, and these event handler by default run inside interrupt handlers. I learned that this is very problematic when it came to writing to flash, because, like every microcontroller I've used, writing to flash is slow, so you must wait for the first flash operation to finish (the erase), before you can start the second (writing the new value). The signals that occur when the flash operations are complete are interrupt driven, so if you are executing flash operations inside an interrupt (the characteristic value changed event in my case), the flash operation interrupt event handler never runs since interrupts are disabled inside the original interrupt handler. The solution to this is to run the application code outside of the interrupt handlers, in what is called the application context. The mesh software does this in a loop in main, where execution waits for some event to occur with sd_app_evt_wait(), and the loop wakes up on any event triggered in the system. The code checks to see if it was a mesh event, and if so, dispatches it for processing. Nordic has a generalized version of this called the Scheduler. The event generating subsystems are configured to use the scheduler, and when sd_app_evt_wait() wakes up, app_sched_execute() dispatches to the event handlers executing in the application context. At this point, the interrupts required for the flash operations work.
I had been using an ST-LINK v2 programmer and openocd to flash the devices, but I ended up buying a Segger J-Link EDU version ($60), because they support RTT debugging. RTT allow printfs to a debug viewer, and is easier to deal with than the alternative of using a serial to USB device and sending the debug output to serial. The problem I've found is RTT works in every case, except when I start to use the uart. When I send a couple of bytes the the uart, RTT stops, although the device continues to function (I can still turn on and off the light). I submitted a questions to the Nordic Developers site, but I haven't gotten any answer.
The other thing to know about flashing is I have to make sure to not erase the last page of flash because that is where I store the device value handle configuration. The Makefile shows how this is done.