Controlling a Quest M6 Roaster with Phidgets and C++

2026-05-25

I have a Quest M6 coffee roaster. It is a small electric drum roaster that comes with manual phase-control dimmers for the fan and heater. I set up some Phidgets with thermocouples to report the bean temperature and roasting environment temperature to Artisan. When I adjusted the heater setting I would also manually enter the setting change in Artisan. I got to thinking it would be nice if I could just enter the desired change in Artisan and that would also set the heater. So the journey begins.

Since I was already using Phidgets for temperature readings, I got a Digital Output Phidget that can output a PWM (pulse-width modulated) signal. The Digital Output Phidget doesn’t have a zero crossing detector to synchronize with the AC grid power, so I planned to use a zero-crossing SSR to switch at a frequency much lower than the 60 Hz grid. Unfortunately I hadn’t read the specifications carefully enough, and realized after I got it and started testing it out that the minimum PWM frequency from the Digital Output Phidget is 100 Hz. It doesn’t really work to use a 100 Hz signal to switch 60 Hz power at the zero crossings. What to do now?

I could have gotten a different device at this point, like a TC4 or a Yocto-PWM-Tx. However, I didn’t really feel like spending more money, and I had gotten an idea to make what I already had work. I could bit bang the PWM instead of using the Digital Output Phidget’s PWM functionality. All it would take is a little programming to communicate with Artisan and to turn the output on and off with the right timing.

Artisan can talk directly to Phidgets, but it can also talk to other devices. One of the protocols it can talk is JSON over WebSockets. I could build a WebSocket server that would talk to the Phidgets, and Artisan could talk to my server.

I thought briefly about using this as an opportunity to learn Go or Rust or Zig or some other language, but I wanted something quickly and I knew performance (low latency specifically) would matter, so I went with C++. I’m not an expert C++ programmer, but I have enough experience that I was confident I could do what I needed to do. My experience with C++ has largely been with KDE applications and libraries, so it was an interesting change to build a server program instead.

There is a C library for communicating with Phidgets, so that was easy enough to interface with from C++. I made a wrapper class to handle the details of working with the Phidgets C API. For WebSocket and JSON support, I chose Boost.Beast and Boost.JSON. I hadn’t worked with Boost libraries directly before, since I had worked mainly with Qt-based software, but I have been happy so far. Beast can do much more than I am using it for. There are several different APIs you can use and it was a bit hard to wrap my head around them all at once. I’m using the synchronous API for now since it seemed the easiest to understand.

I implemented pulse-width modulation by starting a thread and passing the duty cycle in as a reference to a number between 0 and 1, stored in a std::atomic so I could change it atomically from a different thread. The PWM thread runs a loop turning the output on, sleeping for the period duration multiplied by the duty cycle (between 0 and 1), then turns the output off and sleeps for the period multiplied by 1 minus the duty cycle. I’m not sure if there’s a better way to implement this than sleeping the thread, but it’s working for me.

Then I got to thinking again. I was using a fixed length period of 0.83 seconds to cover 50 full cycles of 60 Hz, so when the duty cycle was at 0.5, the heater would be on for 25 cycles and off for 25 cycles. The period could be made shorter on average be implementing a duty cycle of 0.5 as 1 cycle on, 1 cycle off, and 0.75 would be 3 cycles on, 1 cycle off, etc. This is pulse-density modulation instead of pulse-width modulation.

The duty cycle setting comes from Artisan as a number between 0 and 100, so rather than converting that to a floating point number between 0 and 1, I simplified the fraction of the duty cycle over 100, e.g. 75 over 100 becomes 3 over 4, which means 3 cycles on and 4 - 3 = 1 cycle off. The heater control thread received a pair of integers instead of a single floating point number, and multiplied them by 1/60th of a second (full wave of 60 Hz power) to get the on and off durations.

An electric heater responds so slowly that shortening the period like this doesn’t make much difference in controlling it. The main advantage was that it was usually switching fast enough that the watt meter I plugged my roaster into read the average wattage being used. However, the ceiling lights dim when the heater is on full power, and switching like that makes them flicker annoyingly. For now I decided to go back to PWM at a fixed period of 1.67 seconds (100 cycles of 60 Hz), which is slow enough that the lights dimming and brightening doesn’t bother me. The watt meter reading now jumps up and down instead of reading an average, but I don’t rely on it anymore anyway, so that’s alright.

You can see the code at https://codeberg.org/mrp/ossicle. There is currently some stuff hard coded for my own use: my Phidget setup and the port the WebSocket server runs on. If others want to use it and would rather have that configurable some other way than recompiling, open an issue or PR. I may try reimplementing it in a different language for a learning experience sometime, but I’m happy with the project for now.

I’m very happy with how easy the Quest M6 is to modify for projects like this. It is very well built, easy to understand, and easy to work on. It works great in stock condition as a manual roaster and also makes a good base for modifications.