I kicked Alexa out of my house about a week ago. She was frustrating more than helping. And I switched my motorized window blinds over to a simple timer system controlled by the same Raspberry Pi Pico boards as before. The code was very simple. Maybe 500 lines. I moved all of the blinds control code into a blinds_motors class. The public methods were just a constructor that initialized the GPIO pins and state variable, an open command, and a close command. Then the real-time clock in int main() set timers and alarms to open the blinds half an hour after sunrise and close the blinds half an hour before sunset, just like Alexa had done. And after an hour or two of coding the new system did all of those things. But then it also did extra things.
The first night after I wrote the new code, I feel asleep early, around 9pm. The blinds had closed themselves at about 5pm, just like they were supposed to. I woke up at 9:44 to the sound of motors and gears grinding and looked over and saw that the blinds were still closed, and were attempting to close themselves again. The blinds motors were just turning the blinds control rods beyond their limits and the gears were popping. It was doing the regular close sequence, five and a half turns in the close direction, at the normal speed. Except it shouldn't be able to do that. The blinds_motors class keeps track of the blinds position and if you call blinds.close() when the blinds are already closed, nothing should happen. I was surprised and confused, and I made note of the time so I could check later whether that was significant, and then I rolled over and went back to sleep. I woke up at 2am to the sound of the blinds attempting to close yet again. What the hell. The blinds in my bedroom and the living room were doing the same thing at the same exact time. I unplugged them both and went back to sleep again.
The next morning I started digging through the code. The fact that two different controllers did the same unexpected behavior at the exact same time twice ruled out hardware failures. All of the code looked fine. There were no loose ends. The code was so incredibly simple. The only way to change the blinds position value in memory was to call the constructor or to call the open or close functions. The state variable itself is private. So I instrumented the code. I added statements that printed to serial and I also added LED pulses. The LED was programmed to blink every second when the clock ticked and then go solid-on when the blinds were turning which takes about two seconds. Then I plugged the controller back in. The blinds closed like they were supposed to. Ten seconds later, they tried to close again. No serial print statements aside from the tick of the clock every second. No LED solid-on. On top of that, serial was printing clock updates and the LED was flashing every second while the blinds were moving, which should be impossible because blinds.open() should block the every-second print and flash. Ten seconds later, it attempted to close for a third time and there were still no debug print statements and none of the expected LED pulses. How on earth is something triggering the correct motor control sequence without triggering the print statements or LED flashes that were written into the same functions?
I don't have an answer for you. I asked all of the LLMs I'm familiar with and they all went in circles. The best explanation I could come up with was that perhaps the blinds.close() functionality was stored in a stack but not erased, and RTC did something incorrect with memory management and started executing old stack commands? I don't know. There was no dynamically allocated memory anywhere in the application. I got rid of the blinds_motors class and converted all of that code back to separate functions. So far, so good. No more unexpected behavior in the past 24hrs. I can't imagine how what I saw could have happened. Maybe it was Alexa's revenge.