Project tutorial

Alexa Enabled USB Power Switch © CC BY-SA

Many devices are powered by USB power now, but few home automation systems provide USB power switching. "Alexa, turn off the ThingyStick!".

  • 22 respects

Components and supplies

Necessary tools and machines

3D Printer (generic)
09507 01
Soldering iron (generic)

Apps and online services

About this project

Alexa, Turn on the ThingyStick...

Alexa controlling USB powered things.


More and more devices are powered by USB, many of these tend to be "dumb" devices without internet connectivity or home automation options (e.g. LED lights), how can we automate or control them remotely? Perhaps a USB supply plugged into a mains switch, that's a bit overkill, especially if we want 4 devices controlled individually!

This project allows you to control the power supply to USB powered devices via the internet, and by using the Tinamous SmartHome Skill for Alexa, you can enable voice control for your dumb USB devices.

Currently their are very few devices on the market to control USB power remotely, when I started this project I didn't find any (in the UK), but a couple have recently appeared as part of multi-gang outlets (yes, the idea started a long time ago, in a galaxy far away), these are not by well known home automation manufacturers, instead by unbranded manufacturers (i.e. ZLD-34UK) so we have the issue of whose device cloud they use, is the data going to China and back, what happens if that manufacturer no longer exists, how secure is it, how usable is the software, will it be in the recycling box next week, and many of the other normal concerns of Internet connected devices, not to mention not being hackable in the same way an open source Arduino powered device can be.

Use Cases

Examples of USB powered devices we might wish to control:

  • USB powered lights
  • Kindle Fire / Chrome cast sticks (esp. in kids rooms to stop them watching TV)
  • USB humidifiers
  • Echo Dot (make Alexa shut itself down for the night? or just reboot the device)
  • Development boards. Need to reboot that project because your codes gone into an infinite loop?
  • Devices that are difficult to access but need to be rebooted occasionally (i.e. sensors in the loft).
  • Devices installed at client premises where we want to ensure it is powered (by measuring the current consumption and the input voltage)

Simple power control: Internet enabled power switching, either via Alexa voice control or other commands through Tinamous. On and Off.

Smart power control: Many USB lamps have touch controls to switch the lamp on, which means we can't switch the light on remotely, but we can switch it off, and a lot of the time that's all we want (trying to sleep, left a lamp on? Going out, want all the lights switched off?).

However, once we've used Alexa to Turn Off the USB lamp, we then have to ask Alexa to Turn On the lamp before we can then switch it on, that's just silly. With smart power enabled, the off command will turn the lamp power off for a few seconds before restoring power to the device. Enough to turn the lamp off, but also allowing normal operation after.

Timer Automation: Have your devices automatically power down at set times. Kids watching Amazon TV on the fire stick to late? Have the USB supply shut down automatically at 8pm.

Power monitoring: If you're developing USB powered hardware you may well want to know how much current you device is taking, particularly when it is first switches on, or you may like to profile battery pack charging. With the on-board INA219 you can monitor current consumption (I managed about 1kHz sampling with little effort). Voltage drop on USB leads at high current can also be a problem, the INA219 monitors the power into the device so we can warn of low voltage. A terminal block is also provided to allow for higher currents and bigger cables to be used.

Power failure: By using the battery option on the MKR 1000 we can monitor the USB supply voltage and send notifications if the input power has failed. This might be useful for remote (off site) solutions that use USB power but need a bit of extra monitoring, or just as a simple mains power fail detection for your house.


This project is fairly simple, an Arduino MKR1000 at the heart, USB power switching using two LM3526M's to provides high side switching and fault detection (low voltage, over current), along with power monitoring (voltage and current) using an INA219, and finally LEDs and switches for local control options.

I had a PCB made at, you could also send the .brd file off to to have some made as well. The Arduino socket has pcb pads either side of each pin to allow for hacking. e.g. you could easily add some sensors for environmental conditions, or a small OLED display to show voltage and current.

In the github repository their is a 2 port and 4 port option. Be-careful with the 2 port PCB as I messed up and got the layout for the USB socket wrong (they are back to front - wrong polarity!).

Victory from the jaws of defeat:

As it turns out mounting the USB socked on the back of the board is actually a nice solution and meant the pin connections were correct (however for the 2 port board it also means the silk screen is on the wrong side!). The Arduino with headers in a socket was a bit pushed for space with the height required to get the USB sockets out of the enclosure so it actually worked out better, I decided to re-do the board with sockets, switches and LEDs on the reverse side and to add two extra ports, hence the four port version was created (also, I had the LEDs horribly aligned on the 2 porter, so that got fixed as well!).

Theirs very little stopping this being extended to a 6 or 8 port switcher, although the LEDs and switches might need to be dropped or improved on.

The schematic looks a lot more complex to build that it is. A lot of the resistors are optional. Resistors R23, 19, 27 and 26 are all pull-ups for the switches, likewise R20-22, R14 & R15 are pull-ups for the USB control and fault detection. These can all be done through the INPUT_PULLUP pin mode in the Arduino, however if you wanted to put the Arduino to low power sleep and use interrupts to wake it, you may wish to populate these so they don't float (and bounce around).

I also added optional resistors around the USB D+/D- lines. These can be fitted to tell the device how much power it can use, but for a lot of dumb devices these are ignored anyway. Below only R24 for the LED current limiting is actually needed. R2-5 and R28 are left empty.

The LEDs and switches are also completely optional. If you just want a standalone box that controls USB remotely don't add those parts.

Power Input

This controller can be powered via three options.

The first is the "Internal" option (JP6 pins 1 & 2 connected), USB power is taken from the Arduino's 5V pin (and hence from the Arduino USB connector). However this should only be used for low power loads.

The other options are for external power (JP6 pins 2 & 3 connected), you can then connect 5V via J2 (the onboard USB micro connector), or JP8 (a terminal block). You shouldn't use both options at the same time. The external power is also routed to the Arduino VIn pin so it won't need its own power option as well.

For bonus points, fitting 0R resistors to R10 and R11, as well as J2 gives USB pass through for USB 3 so we can reboot a USB connected device, very handy when developing hardware and you don't want to wear out your USB sockets on the PC!


A 3D printable enclosure is included with this project. It uses M3 heatfit inserts for PCB and lid connections, although the tolerances on the lid are good enough for a friction fit.

The Lid includes a few options.

  • With or without battery compartment.
  • With or without feet (aka mounting holes to allow the box to be secured to a surface).

Likewise the base box can be configured (using OpenSCAD) to include openings for each of the three power sources.

It took a few goes to get the enclosure right. How do you version your prints? My samples from the Rigid.Ink fan club came in very handy.

Getting Connected to Tinamous

We're using the Arduino MKR1000 to control the outlets, so we have WiFi connectivity. If you're new to this board you'll need to add the board option to your Arduino IDE.

We're going to use the Tinamous MQTT server to connect to, mainly because it's awesome, but also, maybe because I'm the founder/developer/tea maker! We'll subscribe to the status post topic for messages sent to the device. (i.e. the "Status.To" topic).

Before starting coding we need to ensure the Arduino has the latest firmware as well as the required SSL certificates to get a secure connection to Tinamous. Obviously if you prefer you could adapt this to your own local MQTT server and not worry so much about security.

Step 1

Open the Arduino IDE and load the FirmwareUpdater sketch from the WiFi101 examples menu. Upload this to your device.

Step 2

Once uploaded select WiFi101 Firmware Updater from the Tools menu.

Step 3

Test your connection then Update Firmware. Once that's done use the Add domain button and add to ensure the Arduino has the correct SSL certificates in place. Press the Upload Certificates to WiFi module button to upload the certificates.

Step 4

Now we can write our own firmware. You'll find the files attached to this project and also in the GitHub repository (GitHub will have the latest version).

We'll use the WiFi101 and MQTT Client to your Arduino sketch.

Select the MQTT by Joel Gaehwiler option.

The file secrets.h needs to be populates with your WiFi and Tinamous MQTT settings, I've not included my copy for obvious reasons.

WiFi and MQTT Server Settings:

#define SECRET_SSID "Your SSID"
#define SECRET_PASS "Your SSIDs Password"
/************************* Tinamous MQTT Setup *********************************/
#define MQTT_SERVER      "<tinanamous account name>"
#define MQTT_SERVERPORT  8883   
#define MQTT_USERNAME    "UsbSwitch.<tinanamous account name>"
#define MQTT_PASSWORD    "Your password goes in here."
#define MQTT_CLIENT_ID   "UsbSwitch"
#define DEVICE_USERNAME  "UsbSwitch"

If you've not already registered with Tinamous, you can create your own free account here. When you register you are asked for an Account/Organisation name, this becomes your own private area of Tinamous, you can invite other members to that area (including Alexa), and share your devices with your group.

Below I'm calling my account "AlexaExample", this is what I'll need to include in the MQTT setup, and my Tinamous account is at

Step 5

Next up, we need to add our Device. On the Tinamous Devices page, click the Add button.

Hence the Tinamous MQTT settings for my device look something like this...

/************************* Tinamous MQTT Setup *********************************/
#define MQTT_SERVER      ""
#define MQTT_SERVERPORT  8883   
#define MQTT_USERNAME    "UsbSwitch.AlexaExample"
#define MQTT_PASSWORD    "My super secret password that totally isn't Passw0rd...."
#define MQTT_CLIENT_ID   "UsbSwitch"
#define DEVICE_USERNAME  "UsbSwitch"

That's our Tinamous account and device enabled. You can add more devices here if you wish, just update the MQTT settings for the DEVICE_USERNAME and MQTT_USERNAME (MQTT doesn't send header information like http, so Tinamous has no idea which sub-domain you are using, hence we need to specify the account in the username).

Step 6

Upload the attached code to your devices (with your updated secrets.h file). Take a moment to look at the TinamousMQTTClient.ino file, this handles our MQTT interactions, and with that the Alexa commands that are sent to our device.

We need to be using the WiFiSSLClient for SSL. If you wish to use a local MQTT server without SSL you can use the regular WiFi Client and drop down to port 1883, but for anything internet based, use the SSL Client.

We also need to reserve a little more than the default buffer for the MQTT client, here we're reserving 4096 bytes.

WiFiSSLClient networkClient; 
MQTTClient mqttClient(4096); 

We use the mqttClient.begin to set-up the client and specify a function handler in onMessage that is called when a message is received from the MQTT server.

 mqttClient.begin(MQTT_SERVER, MQTT_SERVERPORT, networkClient);
 // Handle received messages.

And then we can connect and subscribe to the topic's we are interested in. Here you see we've subscribed to "Tinamous/V1/Status.To/UsbSwitch", we'll receive messages send to @UsbSwitch from the Tinamous timeline. This is how we'll get messages from Alexa.

   if (mqttClient.lastError() == LWMQTT_CONNECTION_DENIED) {
     // This error is because your username or password is wrong
   if (mqttClient.lastError() == -6) {
     // This error is most likely because you didn't add the SSL certificate.
   // Force a delay before re-trying the connection
   delay (10000);
   return false;
// Successful connection. Now subscribe to the topic 
mqttClient.subscribe("/Tinamous/V1/Status.To/" DEVICE_USERNAME);

You can think of the Tinamous timeline like a private version of Twitter for you and your devices, they can use MQTT or the REST API (or one of the bots) to see the messages sent to them and act on them, as well as post messages back.

We've got a little bit more work to do in Tinamous to enable Alexa integration, but for now we can test our device and firmware by using the timeline and sending messages.

If you use the Tinamous SmartHome skill that's all the coding that is needed.

The Tinamous Smart Home Skill for Alexa

The Tinamous Smart Home Skill is currently pending approval to be published (at the time of writing, fingers crossed it's in the store now...). To make your own USB switcher you can just use this Skill when it's available.

The skill is actually very generic, it knows nothing about the devices or how to talk to it. We apply tags to the devices in Tinamous and the skill will create an appropriate device in the Alexa account, hence, you could use this skill and Tinamous to voice enable one of your own projects with just a few lines of code on the device.

However, you may wish to alter things, or write your own skill so I'll share the details of my development.

I wrote the skill in C# for .Net Core 2.0, it was actually developed for my BOFF smart fan project, but for this project I extended it to allow a device to have many outlets (ports) and for each one to be a first class citizen in Alexa Smart Home devices.

All the code (including some tests!) is in the Tinamous SmartHome repository, clone, download or just view as you like. I used Visual Studio 2017 with the AWS tools installed to help push the skill to Lambda.

The framework deserializes the incoming directive message from Alexa into objects that we can access through the code, appropriate actions are taken based on the headers namespace and directive name, and then an appropriate response is returned.

The main messages of interest are:

  • Account Linking

Account Linking:

This is handled by Alexa for us, after we add the skill to our account, Alexa will request authentication, we are taken to the Tinamous Authorize page to allow for the device to access our account. Here you need to enter your account name (AlexaExample in this case), username (Steve) and password (no, I'm not telling!)


Once your account is linked, Alexa prompts to perform discovery, the skill then queries Tinamous for devices that are tagged with "Alexa.SmartDevice". Devices without this are simply ignored.

These tags are applied by editing the device from the Devices page in Tinamous.

If the device is also tagged with "MultiPort" each port is enumerated and a device is also added for those. We also apply tags based on the Alexa directives our device supports, here it's just "Alexa.PowerController" and also a tag to indicate which category the device should be displayed under in the app, here I've used "SmartPlug".

Other interfaces are available, such as Alexa.BrightnessController, but that's not so useful here. See the repository readme for more details.

For our MultiPort device to expose individual ports to Alexa we need to set-up the State Variables, also on the edit device page.

PortCount indicates the number of ports the device has, then "Port-1".."Port-n" give the names that Alexa will use for the ports. Any port without a name will be ignored. You can easily change the name of the port here and re-run discovery to get Alexa updated.

During discovery, the skill will also look for a field tagged, named or labelled "powerState", or "powerState-port-n" as appropriate. If this field is found it is assigned as a supported capability for the device (more on this in State Report).

State Report

During the discover phase we told Alexa what capabilities our device has, the skill defaults to telling Alexa these can be requested, hence Alexa will send a StateReport request to get the values for these.

We have one last set of tags to be applied to support this, this time to the devices fields. Once your device is sending data (That's the senml message that is being pushed across MQTT in the Arduino code), Tinamous will create fields for the device. We can then use the Advanced option from the fields list to edit this. Using the tag option is the most versatile as it means we don't need to get the field name correct in the firmware or stuck if we want to rename it.

If the port specific fields are not found, Alexa will fall back to the non-port specific field (here named powerState).

Power Control

Our device isn't going to be any use in Alexa without this! Alexa will send two directives to our Skill "TurnOn" and "TurnOff". The main function entry point for the skill first looks for the namespace (Alexa.PowerController), then hands the job off to the approriate controller class (PowerController.cs).

The skill then simply publishes a Status Message onto the Tinamous timeline.

For Example:

@UsbSwitch Turn On


@UsbSwitch Turn On port-1

The other supported interfaces are handled in an almost identical way. It's then up to our device to watch for this status message and perform an action. Alexa can then read back the state using a StateReport.

That's how the skill works. Now we just need to push it to AWS Lambda and create a Skill entry on the Alexa console to actually give Alexa access to it.

Creating the AWS Lambda function:

I used the AWS tools within Visual Studio to push the compiled skill to Lambda. But first we need to create the Lambda.

Pro-tip: Create the Lambda in the appropriate AWS area for the language you are supporting. English (UK) needs to target eu-west (Ireland).

Create a new lambda function from scratch. The existing blueprints are limited and out of date (only supporting V2 of the SmartHome interface which is very different and deprecated - we're using V3).

You'll need to create a new role to allow the Lambda to access the resources it needs. Here we select Simple Microservices as a default (it actually gives way more than we need). If you find you are not getting logs in Cloud Watch you may also need to go and give the role permissions for that though the IAM section.

Then we need to publish the binaries from Visual Studio. With the AWS tools installed, right click on the project and select "Publish to AWS Lambda..."

Then fill out the details for the Lambda expression and hit Upload...

Once uploaded we can run some tests on the Lambda to ensure it runs. Then we need to "Publish new version" from the Actions dropdown.

Once published we need to give our Skill permissions to access this,

Click on the "Alexa Smart Home" trigger and enter the skill id (we don't have that just yet though....)

Creating the Skill at the Alexa Console:

With our Lambda almost ready to go we need to head on over to the skill developer console and actually create an entry for it. Open up, select Alexa Skills Kit and add a new Skill.

Select Smart Home Skill API, the language you wish to target (e.g. English (UK) for us in the UK) and select V3 (preferred) payload version.

On the Configuration page we get into the more interesting details. Enter the Lambda ARN from our versioned Lambda (top right of the screen). It should end something like :1 or :2 or :5246 (if you've had a few goes ;-) ).

Whilst in Lambda we can also paste in the "Application ID" or "Skill ID" or "ID" or whatever it's called today from the skill, it should look something like amzn1.ask.skill.2_______________50.

Be sure to click on Save once you've added the Application ID. In the old console you can find the id in the header under the skill name, in the new, erm, open a new window, go back to the console skill list, and click the appropriate link. (it's as if they never used the console to actually make a skill).

To enable account linking we need an OAuth application created at Tinamous, this is done using the Alexa Bot. Head on to your Tinamous account Bots page and add an Alexa Bot. Once you've created that the dialog box will give you all the details you need to configure account linking.

NB: If you're using the (soon to be...) published Tinamous SmartHome skill you don't need an Alexa Bot.

The application created by the bot will allow any other Tinamous account holders to use your skill and link their account. Naturally you don't have to publish your skill and you could keep it private, and they won't be able to see anything in your Tinamous account.

Likewise if you use a different device cloud (seriously, why would you do that ;-) ), then you'll need to enter their OAuth application details.

On the Test page, ensure your skill is set to "Yes" for "Show this skill in the Alexa App".

Save and head on over to (or the one appropriate for your region), or apparently theirs an app as well.....

Click on the "Skills", then top right, "Your Skills", and then "DEV SKILLS", you should see your skill listed with a green marker to indicate it is a development skill.

The "Alexa, turn off the usb light", and additional skill information when you select it is configured on the "Publishing Information" page, you can enter the info during development without having to go for full on publishing.

If you need to update the skill code, upload the new Lambda function, create a new version, re-add the SmartHome trigger and Skill Id, then paste the versioned Lambda function into the skill console (you might be able to use a non-versioned lambda, but it didn't work when I tried - although there were many other things I'd also got wrong at that time).


With our skill code is running in Lambda and the Skill installed in our Alexa account, we can run discovery, and then start controlling the devices.

You can click over to the CloudWatch logs to see the log information written by your skill.

Controlling the humidifier

Had enough of Alexa for the evening....

Skill Debugging & Tips

Alexa provides next to no developer feedback when things go wrong, you can spend ages guessing why Alexa doesn't do as you hoped.

  • Avoid most things other than alpha-numerics in your endpointID's (I used * at one stage and everything broke, but not in an obvious way, however # was fine. Go figure!).
  • Log everything. Check for logs when you've invoked your skill. No log then it's probably authentication, or your skill isn't authorized for the Lambda expression.
  • Use matching AWS and Alexa email accounts.
  • Keep the Lambda in the same region as the skill (Ireland for UK skills).
  • If your skill is invoked but Alexa isn't responding to it (i.e. not listing devices), your response format is probably wrong.
  • If you don't want to use your day to day Alexa account you can use Amazon households to add a second adult account. Note however this appears to be limited to 2 adults, and you can't change it without an epic delay.
  • You can ask "Alexa, Switch Profiles" to switch between your developer and regular profiles.
  • I hope with the redesign of the Alexa Console some better debugging information will be available. However right now all I see is bigger boxes and a little better validation. Some logs are available but they are just success logs. aka management level feel good metrics, and delayed by 36 hours which isn't helpful for debugging!

One Skill, Any (IoT) Thing!

With the Tinamous SmartHome skill, as long as your (Internet of Things) thing can connect to the Tinamous MQTT server (or is connected via a supported bot) adding Alexa SmartHome control to your thing is simply a few lines of code to handle messages like "Turn On", "Turn Off", "Set brightness 20". Just add the tags in Tinamous, and....

"Alexa, Discover Smart Home Devices".

Yes my desk "overflow" is that messy!


This is the main file.
#include <MQTTClient.h>
#include <system.h>
#include <WiFi101.h>
#include <Adafruit_INA219.h>
// Provide your own Secrets.h with WiFi and Tinamous definitions in it.
#include "Secrets.h"

// ================================
#define MAX_USB_PORTS 2

// 2 Port board.
int led_pin[MAX_USB_PORTS][2] = {{A5, A6},{0,1}};
int switch_pin[] = {A0, A1};

// Usb switch B channel -> pin 5 == USB1
int usb_enable_pin[] = {2, 5};
int usb_fault_pin[] = {3, 4};
bool usb_port_state[] = {false, false};
bool usb_power_mode[] = {0, 0, 1, 1};

// ---------------------------------

// 4 Port board.
#define MAX_USB_PORTS 4

// The 2  port board has bi-color LEDs, 4 port doesn't so use same pin......
// LEDs: TX, Rx, D7, D6 (1-4)
int led_pin[MAX_USB_PORTS][2] = {{14, 14}, {13,13}, {7,7}, {6,6}};
int switch_pin[] = {A0, A1, A2, A3};

// USB Enable pins, D1, D3, D4, D5 (not in that order)
int usb_enable_pin[] = {5, 4, 1, 3};
// Single fault pin (D2)
int usb_fault_pin[] = {2, 2, 2, 2};

bool usb_port_state[] = {false, false, false, false};

// Mode: 0 - Switch off as requested.
// Mode: 1 - Switch off, then on after a delay (useful for LED lights that have touch controls).
int usb_power_mode[] = {1, 0, 0, 0};

// ================================

// Automation options.
// time (millis) that the USB port should be switched on/off at.
// zero indicates ignore.
unsigned long usb_power_switch_on_at[] = {0, 0, 0, 0};

// Set > 0 to get processed on first loop.
// Side effect is switching them off that those in smart power 
// mode will schedule to come back on again).
unsigned long usb_power_switch_off_at[] = {1, 1, 1, 1};

// Current monitor INA219
Adafruit_INA219 ina219;

// Flag to indicate if one or more USB ports has a fault.
bool has_usb_fault = false;

// Measured power .
float shunt_voltage = 0;
float bus_voltage = 0;
float current_mA = 0;
float load_voltage = 0;
float power_mW = 0;

// Min/max 
float max_current_mA =0;
float max_busvoltage = 0;
float min_busvoltage = 20;

// track power usage.
unsigned long last_measurement_time = 0;

bool power_failed = false;
bool over_current = false;

// MQTT publishing.
// Probably want it quicker when powering devices.
// or slower when not!
int update_interval_seconds = 20;
unsigned long next_message_send_at = 0;

// =================================================
// Main setup entry point
// =================================================
void setup() {
  digitalWrite(LED_BUILTIN, HIGH);
  for (int channel=0; channel<MAX_USB_PORTS; channel++) {

  //Initialize serial and wait for port to open:
  // Give us a small delay to open the serial monitor...

  // Setup the current sensor.



  // Give it a few seconds to warm up!

  Serial.println("Alexa Enabled USB Power Switched v0.4 Setup Complete");

// Setup the IO pins for the usb port.
void setupChannel(int channel) {
  // Setup the LEDs.
  pinMode(led_pin[channel][0], OUTPUT);
  digitalWrite(led_pin[channel][0], LOW);

  if (led_pin[channel][0] != led_pin[channel][1]) {
    pinMode(led_pin[channel][1], OUTPUT);
    digitalWrite(led_pin[channel][1], LOW);

  // Setup the Switches
  pinMode(switch_pin[channel], INPUT_PULLUP);

  pinMode(usb_enable_pin[channel], OUTPUT);
  digitalWrite(usb_enable_pin[channel], HIGH);

  // This is the same pin on all channels for the 4 channel device.
  pinMode(usb_fault_pin[channel], INPUT_PULLUP);

// Show setup state 1..4 complete 
// can't do steps 3 & 4 on 2 port board.
void showSetupStageCompleted(int stage) {
  if (stage > MAX_USB_PORTS) {

  // Light up the LED (1..4) based on the setup progress.
  SetLeds(stage-1 , true, true);

// ===================================================
// Main Loop
// ===================================================
void loop() {

  // 4 port device, Debug LED is port 4 LED.
  // if any port 4 is on, then don't switch off the LED.
  // with all 4 ports off, the debug LED can blink as it likes...
  if (!hasPoweredPorts()) {
    digitalWrite(LED_BUILTIN, LOW);

  // Check for a fault.
  bool faulted = false;
  for (int channel = 0; channel < MAX_USB_PORTS; channel++) {
    if (checkFault(channel)) {
      faulted = true;

    // Lets us clear the has_usb_fault if ALL of the channels are not faulted.
    has_usb_fault = faulted;

  // Do any automation (e.g. switch on at a certain time)

  // Check the current, voltage and all that.

  // Check for a power fail.



void doDelay() {
  if (hasPoweredPorts()) {

  // Debug only. LED is on Port 4 LED so is shown to the user when 
  // all ports are off.
  // Blinky only when in slow mode so we don't have to delay the
  // reads for the user to see the blinkyness.
  // No ports on, don't update so often.
  digitalWrite(LED_BUILTIN, HIGH);

// Check fr user pressing the switch to turn on a port.
void checkSwitchPressed(int channel) {
  if (isSwitchPressed(channel)) {
    // Toggle the USB power
    setUsb(channel, !usb_port_state[channel]);

// Switch press logic to debounce the switch
bool isSwitchPressed(int channel) {
  // Switch is active low.

  // Debounce delay to ensure the switch is properly pressed.
  // Switch is active low. false = pressed.
  if (digitalRead(switch_pin[channel])) {
    return false;

  // wait to see if it's still pressed after 0.2s

  if (!digitalRead(switch_pin[channel])) {
    // Little debounce...
    return true;
  return false;

// Read the INA219 (Voltage, Current)
void readCurrent() {
  shunt_voltage = ina219.getShuntVoltage_mV();
  bus_voltage = ina219.getBusVoltage_V();
  current_mA = ina219.getCurrent_mA();
  //power_mW = ina219.getPower_mW();
  load_voltage = bus_voltage + (shunt_voltage / 1000);
  // record the measurement time so we can compute 
  // power*time usage.
  last_measurement_time = millis();

  if (current_mA > max_current_mA) {
    max_current_mA = current_mA;

  if (bus_voltage > max_busvoltage) {
    max_busvoltage = bus_voltage;

  if (bus_voltage < min_busvoltage) {
    min_busvoltage = bus_voltage;

// Check we have power input, it's not got a significant voltage drop
// and we're not overloading the input.
void checkPowerSupply() {
  // Higher bus voltage for restored to stop it 
  // going back and forth at 4.19...
  if (power_failed && bus_voltage > 4.5) {
    Serial.print("External power restored. Bus voltage: "); 
    Serial.println(" V");

    publishTinamousStatus("External power has been restored.");
    power_failed = false;
  if (bus_voltage < 4.2 && !power_failed) {
    Serial.print("External power failed! Bus voltage: "); 
    Serial.println(" V.");

    publishTinamousStatus("External power lost!");
    power_failed = true;

  if (over_current && current_mA < 1500) {
    publishTinamousStatus("Current fault cleared!");
    over_current = false;

  if (current_mA > 2000 && !over_current) {
    // Overload!
    publishTinamousStatus("Over Current!");
    over_current = true;

void printPowerWide() {
  Serial.print("Bus Voltage:\t"); 
  Serial.print(" V\t");
  Serial.print("Current:    \t"); 
  Serial.print(" mA\t");
  Serial.print("Max Current:\t"); 
  Serial.print(" mA\t");
  Serial.print("Min Voltage:\t");
  Serial.print(" V\t");
  Serial.print("Max Voltage:\t");
  Serial.print(" V\t");


void printPowerSkinny() {

  for (int channel=0; channel<MAX_USB_PORTS; channel++) {
    Serial.print(usb_port_state[channel] ? "1" : "0" ); 



void resetStats() {
  max_current_mA =0;
  max_busvoltage = 0;
  min_busvoltage = 20;

void sentNextPublishAt(int secondsTime) {
  next_message_send_at = millis() + (secondsTime * 1000);

// ============================================
// Send the device status.
// ============================================
void sendStatus() {
  if (millis() > next_message_send_at) { 
    Serial.println("MQTT publish measurements"); 

    if (power_failed) {
      // reduce how often we send when the power
      // has failed to preserve battery power.
      sentNextPublishAt(update_interval_seconds * 10);
    } else {
    // And do one as senml...
    String senml = "{'e':[";
    // Voltage
    senml = senml + "{'n':'busVoltage'";
    senml = senml + ", 'v':";
    senml = senml + String(bus_voltage);
    senml = senml + ", 'u':'V'}";

    // Max voltage
    senml = senml + ",{'n':'maxBusVoltage'";
    senml = senml + ", 'v':";
    senml = senml + String(max_busvoltage);
    senml = senml + ", 'u':'V'}";

    // Min voltage
    senml = senml + ",{'n':'minBusVoltage'";
    senml = senml + ", 'v':";
    senml = senml + String(min_busvoltage);
    senml = senml + ", 'u':'V'}";
    // Current
    senml = senml + ",{'n':'Current'";
    senml = senml + ", 'v':";
    senml = senml + String(current_mA);
    senml = senml + ", 'u':'mA'}";
    // Max current
    senml = senml + ",{'n':'MaxCurrent'";
    senml = senml + ", 'v':'";
    senml = senml + String(max_current_mA);
    senml = senml + "', 'u':'mA'}";
    // mAh consumed...
    senml = senml + ",{'n':'mAh'";
    senml = senml + ", 'v':";
    senml = senml + String(0); // TODO!
    senml = senml + ", 'u':'mAh'}";
    senml = senml + ",{'n':'powerState";
    senml = senml + "', 'bv':";
    if (isPowered()) {
      senml = senml +  "true";
    } else {
      senml = senml +  "false";
    senml = senml + "}";

    for (int channel=0; channel<MAX_USB_PORTS; channel++) {
      senml = senml + ",{'n':'Port-";
      senml = senml + String(channel+1);
      senml = senml + "', 'v':";
      senml = senml + String(usb_port_state[channel]);
      senml = senml + "}";
    senml = senml +  "]}";

bool isPowered() {
  return !power_failed;

void runAutomation() {
  for (int channel =0; channel < MAX_USB_PORTS; channel++) {
    // Check to see if the port should be powered on automatically
    unsigned long onAt = usb_power_switch_on_at[channel];
    if (onAt > 0 && onAt < millis()) {
      Serial.print("Auto on for port: ");
      Serial.println(channel + 1);
      setUsb(channel, true);
      usb_power_switch_on_at[channel] = 0;

    // Check to see if the port should be powered off automatically
    unsigned long offAt = usb_power_switch_off_at[channel]; 
    if (offAt > 0 && offAt < millis()) {
      Serial.print("Auto off for port: ");
      Serial.println(channel + 1);
      setUsb(channel, false);
      usb_power_switch_off_at[channel] = 0;
This file handled the MQTT connectivity (i.e. it's the bit that responds to Alexa commands).
// ======================================
// Tinamous connectivity via MQTT
// ======================================
#include <MQTTClient.h>
#include <system.h>
#include "secrets.h"

// WiFi and MQTT settings in Secrets.h
// Be sure to use WiFiSSLClient for an SSL connection.
// for a non ssl (port 1883) use regular WiFiClient.
//WiFiClient networkClient; 
WiFiSSLClient networkClient; 

// Specifying 4096 bytes buffer size
MQTTClient mqttClient(4096); 

// If we have been connected since powered up 
bool was_connected = false;
String senml = "";
unsigned long nextSendMeasurementsAt = 0;

// =================================================
// Setup the MQTT connection information
// =================================================
bool setupMqtt() {
  Serial.print("Connecting to Tinamous MQTT Server on port:");
  mqttClient.begin(MQTT_SERVER, MQTT_SERVERPORT, networkClient);

  // Handle received messages.


// =================================================
// Connect to the MQTT server. This can be called 
// repeatedly and will be ignored if already connected
// =================================================
bool connectToMqttServer() { 
  if (mqttClient.connected()) {
    return true;


  Serial.println("checking wifi..."); 
  if (WiFi.status() != WL_CONNECTED) { 
    Serial.print("WiFi Not Connected. Status: "); 
    Serial.print(WiFi.status(), HEX); 
    return false;
  Serial.println("Connecting to MQTT Server..."); 
  if (!mqttClient.connect(MQTT_CLIENT_ID, MQTT_USERNAME, MQTT_PASSWORD)) { 
    Serial.println("Failed to connect to MQTT Server."); 
    Serial.print("Error: "); 
    Serial.print(", Return Code: "); 

    if (mqttClient.lastError() == LWMQTT_CONNECTION_DENIED) {
      Serial.println("Access denied. Check your username and password. Username should be 'DeviceName.AccountName' e.g. MySensor.MyHome"); 

    if (mqttClient.lastError() == -6) {
      Serial.println("Check your Arduino has the SSL Certificate loaded for"); 
      // Load the Firmware Updater sketch onto the Arduino.
      // Use the Tools -> WiFi Firmware Updater utility

    // Wait 10s before it gets re-tried.
    return false;
  Serial.println("Connected to Tinamous MQTT!"); 
  mqttClient.subscribe("/Tinamous/V1/Status.To/" DEVICE_USERNAME); 
  Serial.println("Subscribed to topic."); 
  // Say Hi.
  publishTinamousStatus("Hello! Usb switch is now connected. @ me with help for help.");

  was_connected = true;
  return true;

// =================================================
// Loop for mqtt processing.
// =================================================
void mqttLoop() {
  // Call anyway, does nothing if already connected.

// =================================================
// Publish a status message on the Tinamous timeline
// =================================================
void publishTinamousStatus(String message) {
  Serial.println("Status: " + message);
  mqttClient.publish("/Tinamous/V1/Status", message); 

// =================================================
// Publish measurements using the plain json format
// =================================================
void publishTinamousJsonMeasurements(String json) {
  Serial.println("Measurement: " + json);
  mqttClient.publish("/Tinamous/V1/Measurements/Json", json); 

// =================================================
// Publish measurements using senml json format
// =================================================
void publishTinamousSenMLMeasurements(String senml) {
  Serial.println("SenML Measurement: " + senml);
  mqttClient.publish("/Tinamous/V1/Measurements/SenML", senml); 
  if (mqttClient.lastError() != 0) {
    Serial.print("MQTT Error: "); 
// =================================================
// Message received from the MQTT server
// =================================================
void messageReceived(String &topic, String &payload) { 
  Serial.println("Message from Tinamous on topic: " + topic + " - " + payload); 

  // If it starts with @ it's a status message to this device.
  if (payload.startsWith("@")) {
    if (handleStatusMessage(payload)) {
      Serial.println("@ me status message handled.");

  // Didn't get an expected command, so the message was to us.
  // Publish a help message.
  publishTinamousStatus("Hello! Sorry I didn't understand the message. @ me with help for help.");

// =================================================
// Message was a Status Post (probably Alexa)
// =================================================
bool handleStatusMessage(String payload) {
char buffer[25];

   // for 1..4 (maps to 0..3)
  for (int port = 0; port <MAX_USB_PORTS; port++) {
    sprintf(buffer, "turn on port-%01d", port + 1);
    if (payload.indexOf(buffer)> 0) {
      Serial.print("Turn on port ");
      setUsb(port, true);
      return true;

    sprintf(buffer, "turn off port-%01d", port + 1);
    if (payload.indexOf(buffer)> 0) {
      Serial.print("Turn off port ");
      setUsb(port, false);
      return true;

  // No port specified, turn on all.
  if (payload.indexOf("turn on")> 0) {
    return true;

  if (payload.indexOf("turn off")> 0) {
    return true;

  if (payload.indexOf("help")> 0) {
    Serial.println("Sending help...");
    "Send a message to me (@" DEVICE_USERNAME ") then:" 
    "'Turn on Port-1' to turn on usb port 1,"
    "'Turn off Port-1' to turn off usb port 1,"
    "'Turn on' to turn on all usb ports,"
    "'Turn off' to turn off all usb ports,"
    " \n* Port number can be 1, 2, 3 or 4."
    return true;

  Serial.print("Unknown status message: ");
  return false;
This file is responsible for the iterations with the USB power switching.
// ======================================
// USB Port power control
// and indication.
// ======================================

// Determine if one or more of the ports is powered
bool hasPoweredPorts() {
  for (int channel = 0; channel < MAX_USB_PORTS; channel++) {
    if (usb_port_state[channel]) {
      return true;
  return false;

// Check if the USB devices have a fault (we can't
// tell which one in the 4 port 
bool checkFault(int channel) {
  if (!digitalRead(usb_fault_pin[channel])) {
    // 4 port has a single fault indicator, don't keep checking
    if (has_usb_fault) {
      return true;
    Serial.print("FAULT! usb: ");

    // Switch off the USB channel.
    setUsb(channel, false);
    // Make the LEDs red.
    SetLeds(channel, true, false);

    if (!has_usb_fault) {
      // TODO: Send the message once only!
      //String message = "Fault detected on USB Port ";
      //message = message + String(channel + 1);
      has_usb_fault = true;

    return true;
  return false;

// Switch on ALL of the USB ports
void allOn() {
  for(int channel =0; channel < MAX_USB_PORTS; channel++) {
    setUsb(channel, true);

// Switch off ALL of the USB ports
void allOff() {
  for(int channel =0; channel < MAX_USB_PORTS; channel++) {
    setUsb(channel, true);

// ================================================
// Set the USB output state
// ================================================
void setUsb(int channel, bool on) {
  Serial.print("Switch USB channel ");
  Serial.print(" to ");
  if (on) {
    SetLeds(channel, on, true);
    digitalWrite(usb_enable_pin[channel], LOW);
  } else {
    SetLeds(channel, on, true);
    digitalWrite(usb_enable_pin[channel], HIGH);

  usb_port_state[channel] = on;

  String message = "USB Port ";
  message = message + String(channel + 1);
  message = message + " is now ";
  if (on) {
    message = message + "enabled.";
  } else {
    message = message + "disabled.";
  // force a send of the current/voltage after a second or few.

  // If the channel mode is to cycle off then on for 
  // an off signal, switch the channel back on after a few seconds delay.
  if (!on && usb_power_mode[channel] == 1) {
    // Schedule the channel to come back on in n seconds.
    usb_power_switch_on_at[channel] = millis() + 5000;

// ================================================
// Set the state leds to indicate channel on/off
// They are not connected to the USB switch so 
// can be run independantly.
// ================================================
void SetLeds(int channel, bool on, bool green) {
  if (!on) {
    digitalWrite(led_pin[channel][0], LOW);
    digitalWrite(led_pin[channel][1], LOW);

  if (led_pin[channel][0] == led_pin[channel][1]) {
    // 4 Port uses single color LED.
    // We don't have a way to show faultes (other than flashing LEDs).
    digitalWrite(led_pin[channel][0], HIGH);
  } else {
    // 2 Port uses Bi-color LED and individual faults
    // so show red/green based on status.
    if (green) {
      digitalWrite(led_pin[channel][0], HIGH);
      digitalWrite(led_pin[channel][1], LOW);
    } else {
      digitalWrite(led_pin[channel][0], LOW);
      digitalWrite(led_pin[channel][1], HIGH);
This file deals with WiFi Connectivity
// ======================================
// WiFi handling
// ======================================
#include "Secrets.h" 

char ssid[] = SECRET_SSID;    
char pass[] = SECRET_PASS;    
int status = WL_IDLE_STATUS; 

void setupWiFi() {
  Serial.println("Connecting to WiFi...");

  // check for the presence of the shield:
  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
    // don't continue:
    while (true);

  // attempt to connect to WiFi network:
  while ( status != WL_CONNECTED) {
    Serial.print("Attempting to connect to WPA SSID: ");
    // Connect to WPA/WPA2 network:
    status = WiFi.begin(ssid, pass);

    // wait 10 seconds for connection:

  // you're connected now, so print out the data:
  Serial.println("You're connected to the network");

// ---------------------------------------
// WiFi
void printWiFiData() {
  // print your WiFi shield's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");

  // print your MAC address:
  byte mac[6];
  Serial.print("MAC address: ");
  Serial.print(mac[5], HEX);
  Serial.print(mac[4], HEX);
  Serial.print(mac[3], HEX);
  Serial.print(mac[2], HEX);
  Serial.print(mac[1], HEX);
  Serial.println(mac[0], HEX);


void printCurrentNet() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");

  // print the MAC address of the router you're attached to:
  byte bssid[6];
  Serial.print("BSSID: ");
  Serial.print(bssid[5], HEX);
  Serial.print(bssid[4], HEX);
  Serial.print(bssid[3], HEX);
  Serial.print(bssid[2], HEX);
  Serial.print(bssid[1], HEX);
  Serial.println(bssid[0], HEX);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");

  // print the encryption type:
  byte encryption = WiFi.encryptionType();
  Serial.print("Encryption Type:");
  Serial.println(encryption, HEX);

String hostName = "";

void doPing() {
  Serial.print("Pinging ");
  Serial.print(": ");

  int pingResult =;

  if (pingResult >= 0) {
    Serial.print("SUCCESS! RTT = ");
    Serial.println(" ms");
  } else {
    Serial.print("FAILED! Error code: ");
void reconnectWiFi() {
  // attempt to reconnect to WiFi network if the connection was lost:
  while ( status != WL_CONNECTED) {
    Serial.print("Attempting to connect to WPA SSID: ");
    // Connect to WPA/WPA2 network:
    status = WiFi.begin(ssid, pass);

    if (status == WL_CONNECTED) {
      Serial.print("You're re-connected to the network");

You need to update this with your own settings.
#define SECRET_SSID "<WiFi access point>"
#define SECRET_PASS "<WiFi Password>"

/************************* Tinamous MQTT Setup *********************************/

#define MQTT_SERVER      "<Your account name>"
#define MQTT_SERVERPORT  8883   
#define MQTT_USERNAME    "<Device Username>.<Your account name>"
#define MQTT_PASSWORD    "The devices password"
#define MQTT_CLIENT_ID   "A random client id"
#define DEVICE_USERNAME  "<Device Username>"

Custom parts and enclosures

Enclosure - Base - All Connectors Open
This is the best bet if you don't know how you'll connect. It's got holes for the Arduino, terminal block and additional USB micro.
Enclosure - Base - All Holes - No Text
This is the all connectors version, but doesn't include the first layers of text which some printers may struggle with.
Lid - With Battery Compartment - No Feet
Lid - Battery Compartment - With Feet
Lid - No Battery - No Feet
This is the simplest version and quickest to print.
Lid - No Battery - With Feet


PCB - All layers
Pcb all 4port 9u56aitzev
PCB Bottom
Pcb bottom 4port i0o9tszvwh
PCB (Eagle)
Send this off to OSHPark or DirtyPCBs to get your own one made.
Schematic (Eagle)
Schematic 4port lkas078ukn
GitHub USB Power Switcher Repository
You want: Arduino/StandAlone/ folder and the 4 port version.
PCB - Top
Pcb top 4port 7drsbhfkkw


Similar projects you might like

Jrobot Delivery

Project showcase by joechen

  • 21 respects

Mask IT

Project in progress by Mehak Beri

  • 9 respects

Simple Wireless Keyboard for PC

Project showcase by Gyro1

  • 33 respects

OldArduiPhone (or the Hipster Cellphone!)

Project showcase by Jeremie

  • 26 respects

Two Ultrasonic Sensor Arduino Radar - Continuous Rotation

Project showcase by MicroLab Greece

  • 16 respects

BOFF - Alexa Enabled Open Smart Fan

Project showcase by Stephen Harrison

  • 21 respects
Add projectSign up / Login