Alex Merchen
Published © GPL3+

Sprinkler Control System Using AVR-IoT Cellular Mini

Using weather data and direct soil measurement using the AVR-IoT Cellular mini, create a smart sprinkler system!

IntermediateFull instructions provided5 hours228

Things used in this project

Hardware components

AVR IoT Mini Cellular Board
Microchip AVR IoT Mini Cellular Board
×1
DHT22 Temperature Sensor
DHT22 Temperature Sensor
×1
SparkFun Soil Moisture Sensor (with Screw Terminals)
SparkFun Soil Moisture Sensor (with Screw Terminals)
×1
Raspberry Pi Pico W
Raspberry Pi Pico W
×1
SparkFun 12V Solenoid Valve - 3/4"
×1
60W PCIe 12V 5A Power Supply
Digilent 60W PCIe 12V 5A Power Supply
×1
Li-Ion Battery 1000mAh
Li-Ion Battery 1000mAh
×1
Relay (generic)
×1

Software apps and online services

Arduino IDE
Arduino IDE
Adafruit IO
Maker service
IFTTT Maker service

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
Breadboard, 400 Pin
Breadboard, 400 Pin

Story

Read more

Schematics

AVR-IoT Cellular Mini Circuit

Raspberry Pi Pico W Wiring Schematic

Code

mqtt_custom_broker - Modified for Adafruit IO

Arduino
No preview (download only).

Phase 2 - Transmitting sensor data to Adafruit IO using AVR-IoT Cellular Mini

Arduino
#include <Arduino.h>

#include <ecc608.h>
#include <led_ctrl.h>
#include <log.h>
#include <lte.h>
#include <mqtt_client.h>

#include <DHT.h>

// Adafruit IO MQTT settings
#define AIO_USERNAME    "XXXX" // Replace with your actual Adafruit IO username
#define AIO_KEY         "XXXX"     // Replace with your actual Adafruit IO key

// Define Adafruit IO MQTT topics for subscribing and publishing
// Structure is USERNAME/feeds/FEEDNAME
#define MQTT_SUB_TOPIC AIO_USERNAME "/feeds/lawn-sprinkler.input"
#define MQTT_PUB_TOPIC AIO_USERNAME "/feeds/lawn-sprinkler.output"

#define MQTT_THING_NAME "Your_Thing_Name" // Can be anything that identifies your device
#define MQTT_BROKER     "io.adafruit.com" // Adafruit IO Broker
#define MQTT_PORT       1883              // Use 8883 for SSL connection
#define MQTT_USE_TLS    false             // Set true if using SSL
#define MQTT_USE_ECC    false             // Not typically used with Adafruit IO
#define MQTT_KEEPALIVE  60

#define DHTPIN 38
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

int chk;
float hum;
float temp;

#define PROBE A0
int probeValue;

static uint32_t counter = 0;

void setup() {
    Log.begin(115200);
    LedCtrl.begin();
    LedCtrl.startupCycle();

    dht.begin();
    
    Log.info(F("Starting MQTT with Adafruit IO"));

    
    
    // Establish LTE connection
    if (!Lte.begin()) {
        Log.error(F("Failed to connect to operator"));

        // Halt here
        while (1) {}
    }

    // Attempt to connect to Adafruit IO
    if (MqttClient.begin(MQTT_THING_NAME,
                         MQTT_BROKER,
                         MQTT_PORT,
                         MQTT_USE_TLS,
                         MQTT_KEEPALIVE,
                         MQTT_USE_ECC,
                         AIO_USERNAME,    // Username for Adafruit IO
                         AIO_KEY)) {      // AIO Key for authentication
        MqttClient.subscribe(MQTT_SUB_TOPIC);
    } else {
        Log.rawf(F("\r\n"));
        Log.error(F("Failed to connect to Adafruit IO"));

        // Halt here
        while (1) {}
    }

    // Test MQTT publish and receive
    for (uint8_t i = 0; i < 3; i++) {
        probeValue = analogRead(PROBE);
        hum = dht.readHumidity();
        temp= dht.readTemperature();
        
        String message_to_publish = "Humidity: " + String(hum) + " %, Temp: " + String(temp) + " C, Probe: " + String(probeValue);

        bool publishedSuccessfully =
            MqttClient.publish(MQTT_PUB_TOPIC, message_to_publish.c_str());

        if (publishedSuccessfully) {
            Log.infof("Published message: %s\r\n", message_to_publish.c_str());
            counter++;
        } else {
            Log.error("Failed to publish");
        }

        delay(3000);

        String message = MqttClient.readMessage(MQTT_SUB_TOPIC);
        if (message != "") {
            Log.infof("Got new message: %s\r\n", message.c_str());
        }
    }

    Log.info("Closing MQTT connection");
    MqttClient.end();
}

void loop() {}

Phase 2A - Transmitting individual feeds to Adafruit IO from AVR-IoT Cellular Mini

Arduino
#include <Arduino.h>

#include <ecc608.h>
#include <led_ctrl.h>
#include <log.h>
#include <lte.h>
#include <mqtt_client.h>
#include <DHT.h>
#include <low_power.h>

// Adafruit IO MQTT settings
#define AIO_USERNAME    "XXXX" // Replace with your actual Adafruit IO username
#define AIO_KEY         "XXXX"     // Replace with your actual Adafruit IO key

// Define Adafruit IO MQTT topics for subscribing and publishing
// Structure is USERNAME/feeds/FEEDNAME
#define MQTT_SUB_TOPIC AIO_USERNAME "/feeds/lawn-sprinkler.input"
#define MQTT_PUB_TOPIC AIO_USERNAME "/feeds/lawn-sprinkler.output"
#define MQTT_PUB_BATTERY AIO_USERNAME "/feeds/lawn-sprinkler.battery"
#define MQTT_PUB_HUMIDITY AIO_USERNAME "/feeds/lawn-sprinkler.humidity"
#define MQTT_PUB_MOISTURE AIO_USERNAME "/feeds/lawn-sprinkler.moisture"
#define MQTT_PUB_TEMPERATURE AIO_USERNAME "/feeds/lawn-sprinkler.temperature"

#define MQTT_THING_NAME "Your_Thing_Name" // Can be anything that identifies your device
#define MQTT_BROKER     "io.adafruit.com" // Adafruit IO Broker
#define MQTT_PORT       1883              // Use 8883 for SSL connection
#define MQTT_USE_TLS    false             // Set true if using SSL
#define MQTT_USE_ECC    false             // Not typically used with Adafruit IO
#define MQTT_KEEPALIVE  60

#define DHTPIN 38
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

int chk;
float hum;
float temp;

#define PROBE A0
int moisture;

static uint32_t counter = 0;
bool publishedSuccessfully;
String message_to_publish;

float battery_level = 0;

void setup() {
    Log.begin(115200);
    LedCtrl.begin();
    LedCtrl.startupCycle();

    dht.begin();
    
    Log.info(F("Starting MQTT with Adafruit IO"));

    
    
    // Establish LTE connection
    if (!Lte.begin()) {
        Log.error(F("Failed to connect to operator"));

        // Halt here
        while (1) {}
    }

    // Attempt to connect to Adafruit IO
    if (MqttClient.begin(MQTT_THING_NAME,
                         MQTT_BROKER,
                         MQTT_PORT,
                         MQTT_USE_TLS,
                         MQTT_KEEPALIVE,
                         MQTT_USE_ECC,
                         AIO_USERNAME,    // Username for Adafruit IO
                         AIO_KEY)) {      // AIO Key for authentication
        MqttClient.subscribe(MQTT_SUB_TOPIC);
    } else {
        Log.rawf(F("\r\n"));
        Log.error(F("Failed to connect to Adafruit IO"));

        // Halt here
        while (1) {}
    }

    // Test MQTT publish and receive
    for (uint8_t i = 0; i < 3; i++) {
        moisture = analogRead(PROBE);
        hum = dht.readHumidity();
        temp= dht.readTemperature();
        battery_level = LowPower.getSupplyVoltage();
        
        message_to_publish = "Battery Level: " + String(battery_level) + "V, Humidity: " + String(hum) + " %, Temp: " + String(temp) + " C, Probe: " + String(moisture);

        publishedSuccessfully =
            MqttClient.publish(MQTT_PUB_TOPIC, message_to_publish.c_str());

        if (publishedSuccessfully) {
            Log.infof("Published message: %s\r\n", message_to_publish.c_str());
            counter++;
        } else {
            Log.error("Failed to publish");
        }

        
        message_to_publish = String(battery_level);

        delay(2000);
        
        publishedSuccessfully =
            MqttClient.publish(MQTT_PUB_BATTERY, message_to_publish.c_str());

        if (publishedSuccessfully) {
            Log.infof("Published message: %s\r\n", message_to_publish.c_str());
            counter++;
        } else {
            Log.error("Failed to publish");
        }

        message_to_publish = String(hum);

        delay(2000);
        
        publishedSuccessfully =
            MqttClient.publish(MQTT_PUB_HUMIDITY, message_to_publish.c_str());

        if (publishedSuccessfully) {
            Log.infof("Published message: %s\r\n", message_to_publish.c_str());
            counter++;
        } else {
            Log.error("Failed to publish");
        }

        message_to_publish = String(moisture);

        delay(2000);
        
        publishedSuccessfully =
            MqttClient.publish(MQTT_PUB_MOISTURE, message_to_publish.c_str());

        if (publishedSuccessfully) {
            Log.infof("Published message: %s\r\n", message_to_publish.c_str());
            counter++;
        } else {
            Log.error("Failed to publish");
        }

        message_to_publish = String(temp);

        delay(2000);
        
        publishedSuccessfully =
            MqttClient.publish(MQTT_PUB_TEMPERATURE, message_to_publish.c_str());

        if (publishedSuccessfully) {
            Log.infof("Published message: %s\r\n", message_to_publish.c_str());
            counter++;
        } else {
            Log.error("Failed to publish");
        }

        delay(3000);

        String message = MqttClient.readMessage(MQTT_SUB_TOPIC);
        if (message != "") {
            Log.infof("Got new message: %s\r\n", message.c_str());
        }
    }

    Log.info("Closing MQTT connection");
    MqttClient.end();
}

void loop() {}

Phase 2B - Conserving Power

Arduino
#include <Arduino.h>

#include <ecc608.h>
#include <led_ctrl.h>
#include <log.h>
#include <lte.h>
#include <mqtt_client.h>
#include <DHT.h>
#include <low_power.h>

// Adafruit IO MQTT settings
#define AIO_USERNAME    "XXXX" // Replace with your actual Adafruit IO username
#define AIO_KEY         "XXXX"     // Replace with your actual Adafruit IO key

// Define Adafruit IO MQTT topics for subscribing and publishing
// Structure is USERNAME/feeds/FEEDNAME
#define MQTT_SUB_TOPIC AIO_USERNAME "/feeds/lawn-sprinkler.input"
#define MQTT_PUB_TOPIC AIO_USERNAME "/feeds/lawn-sprinkler.output"
#define MQTT_PUB_BATTERY AIO_USERNAME "/feeds/lawn-sprinkler.battery"
#define MQTT_PUB_HUMIDITY AIO_USERNAME "/feeds/lawn-sprinkler.humidity"
#define MQTT_PUB_MOISTURE AIO_USERNAME "/feeds/lawn-sprinkler.moisture"
#define MQTT_PUB_TEMPERATURE AIO_USERNAME "/feeds/lawn-sprinkler.temperature"

#define MQTT_THING_NAME "Your_Thing_Name" // Can be anything that identifies your device
#define MQTT_BROKER     "io.adafruit.com" // Adafruit IO Broker
#define MQTT_PORT       1883              // Use 8883 for SSL connection
#define MQTT_USE_TLS    false             // Set true if using SSL
#define MQTT_USE_ECC    false             // Not typically used with Adafruit IO
#define MQTT_KEEPALIVE  60

#define DHTPIN 38
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

#define USE_POWER_SAVE
int chk;
float hum;
float temp;

#define PROBE A0
int moisture;

static uint32_t counter = 0;
bool publishedSuccessfully;
String message_to_publish;

float battery_level = 0;

void setup() {
    Log.begin(115200);
    LedCtrl.begin();
    LedCtrl.startupCycle();

    dht.begin();

    Log.info(F("Setup Complete"));
    
    
}

void loop() {
  Log.info(F("Starting MQTT with Adafruit IO"));

    
    
    // Establish LTE connection
    if (!Lte.begin()) {
        Log.error(F("Failed to connect to operator"));

        // Halt here
        while (1) {}
    }

    delay(2000);
    
    // Attempt to connect to Adafruit IO
    if (MqttClient.begin(MQTT_THING_NAME,
                         MQTT_BROKER,
                         MQTT_PORT,
                         MQTT_USE_TLS,
                         MQTT_KEEPALIVE,
                         MQTT_USE_ECC,
                         AIO_USERNAME,    // Username for Adafruit IO
                         AIO_KEY)) {      // AIO Key for authentication
        MqttClient.subscribe(MQTT_SUB_TOPIC);
    } else {
        Log.rawf(F("\r\n"));
        Log.error(F("Failed to connect to Adafruit IO"));

        // Halt here
        while (1) {}
    }

    // Test MQTT publish and receive
    for (uint8_t i = 0; i < 2; i++) {
        moisture = analogRead(PROBE);
        hum = dht.readHumidity();
        temp= dht.readTemperature();
        battery_level = LowPower.getSupplyVoltage();
        
        message_to_publish = "Battery Level: " + String(battery_level) + "V, Humidity: " + String(hum) + " %, Temp: " + String(temp) + " C, Probe: " + String(moisture);

        publishedSuccessfully =
            MqttClient.publish(MQTT_PUB_TOPIC, message_to_publish.c_str());

        if (publishedSuccessfully) {
            Log.infof("Published message: %s\r\n", message_to_publish.c_str());
            counter++;
        } else {
            Log.error("Failed to publish");
        }

        
        message_to_publish = String(battery_level);

        delay(3000);
        
        publishedSuccessfully =
            MqttClient.publish(MQTT_PUB_BATTERY, message_to_publish.c_str());

        if (publishedSuccessfully) {
            Log.infof("Published message: %s\r\n", message_to_publish.c_str());
            counter++;
        } else {
            Log.error("Failed to publish");
        }

        message_to_publish = String(hum);

        delay(3000);
        
        publishedSuccessfully =
            MqttClient.publish(MQTT_PUB_HUMIDITY, message_to_publish.c_str());

        if (publishedSuccessfully) {
            Log.infof("Published message: %s\r\n", message_to_publish.c_str());
            counter++;
        } else {
            Log.error("Failed to publish");
        }

        message_to_publish = String(moisture);

        delay(3000);
        
        publishedSuccessfully =
            MqttClient.publish(MQTT_PUB_MOISTURE, message_to_publish.c_str());

        if (publishedSuccessfully) {
            Log.infof("Published message: %s\r\n", message_to_publish.c_str());
            counter++;
        } else {
            Log.error("Failed to publish");
        }

        message_to_publish = String(temp);

        delay(3000);
        
        publishedSuccessfully =
            MqttClient.publish(MQTT_PUB_TEMPERATURE, message_to_publish.c_str());

        if (publishedSuccessfully) {
            Log.infof("Published message: %s\r\n", message_to_publish.c_str());
            counter++;
        } else {
            Log.error("Failed to publish");
        }

        delay(3000);

        String message = MqttClient.readMessage(MQTT_SUB_TOPIC);
        if (message != "") {
            Log.infof("Got new message: %s\r\n", message.c_str());
        }
    }

    

  
    Log.info("Closing MQTT connection");
    MqttClient.end();
    Log.info(F("Power saving..."));
    Lte.end();
    LowPower.powerDown(1000);
    
  
  }

Phase 3B - Raspberry Pi Pico W to Adafruit IO

Arduino
import network
from umqtt.simple import MQTTClient
import time

# Your Wi-Fi credentials
WIFI_SSID = 'Your-SSID'
WIFI_PASSWORD = 'Your-WIFI-PASSWORD'

# Adafruit IO credentials
AIO_USERNAME = 'Your-Adafruit-IO-Username'
AIO_KEY = 'Your-Adafruit-IO-Key'
AIO_FEED = f"{AIO_USERNAME}/feeds/humidity"  # Adjust 'humidity' as needed

# Connect to Wi-Fi
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(WIFI_SSID, WIFI_PASSWORD)

# Wait for connection
while not wlan.isconnected():
    pass

print('Connected to Wi-Fi')

# MQTT Callback Function
def sub_cb(topic, msg):
    print((topic, msg))

# Connect to Adafruit IO
client = MQTTClient(AIO_USERNAME, 'io.adafruit.com', user=AIO_USERNAME, password=AIO_KEY, ssl=True)
client.set_callback(sub_cb)
client.connect()
client.subscribe(AIO_FEED)

print('Subscribed to Adafruit IO feed')

# Wait for and process messages
while True:
    try:
        client.wait_msg()
    except KeyboardInterrupt:
        print('Disconnected from Adafruit IO')
        client.disconnect()
        break

Phase 3C - Code 1

Python
import network
import socket
import json
import time
import machine
import struct

# Constants and Global Variables
WIFI_SSID = 'XXXX'
WIFI_PASSWORD = 'XXXX'
AIO_USERNAME = 'XXXX'
AIO_KEY = 'XXXX'

# Feed keys
HUMIDITY_FEED_KEY = 'lawn-sprinkler.humidity'
TEMPERATURE_FEED_KEY = 'lawn-sprinkler.temperature'
MOISTURE_FEED_KEY = 'lawn-sprinkler.moisture'

# Initialize the pins
sprinkler_pin = machine.Pin(17, machine.Pin.OUT)  # Sprinkler control pin
stop_pin = machine.Pin(21, machine.Pin.IN, machine.Pin.PULL_DOWN)  # Stop signal pin

# Connect to Wi-Fi
def connect_to_wifi():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        print('Connecting to network...')
        wlan.connect(WIFI_SSID, WIFI_PASSWORD)
        while not wlan.isconnected():
            time.sleep(1)
    print('Network connected!')

# Set the time using NTP
def set_time_from_ntp(ntp_server="pool.ntp.org"):
    NTP_DELTA = 2208988800
    client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    addr = socket.getaddrinfo(ntp_server, 123)[0][-1]
    msg = b'\x1b' + 47 * b'\0'
    try:
        client.sendto(msg, addr)
        msg, address = client.recvfrom(1024)
    finally:
        client.close()
    val = struct.unpack("!I", msg[40:44])[0]
    timestamp = val - NTP_DELTA
    tm = time.localtime(timestamp)
    machine.RTC().datetime((tm[0], tm[1], tm[2], tm[6], tm[3], tm[4], tm[5], 0))
    print("Time set successfully.")

# HTTP GET request and parse response
def http_get(url):
    _, _, host, path = url.split('/', 3)
    addr = socket.getaddrinfo(host, 80)[0][-1]
    s = socket.socket()
    s.connect(addr)
    request = f'GET /{path} HTTP/1.0\r\nHost: {host}\r\nX-AIO-Key: {AIO_KEY}\r\n\r\n'
    s.send(bytes(request, 'utf8'))
    response = b""
    while True:
        data = s.recv(100)
        if data:
            response += data
        else:
            break
    s.close()
    return response

# Parse the HTTP response to extract the feed value
def get_last_value_from_response(response):
    try:
        parts = response.split(b'\r\n\r\n', 1)
        if len(parts) > 1:
            header, body = parts
            data = json.loads(body.decode('utf-8'))
            return data['value']
        else:
            print("Malformed response received.")
    except Exception as e:
        print(f'Failed to parse response: {e}')
    return None

# Fetch the last value for a specific Adafruit IO feed
def fetch_last_value(feed_key):
    url = f"http://io.adafruit.com/api/v2/{AIO_USERNAME}/feeds/{feed_key}/data/last"
    response = http_get(url)
    return get_last_value_from_response(response)

# Main function incorporating logic based on the feed values
def main():
    connect_to_wifi()
    set_time_from_ntp()

    print("Script is running. Set GPIO 21 high to stop.")

    while stop_pin.value() == 1:  # Continue as long as the stop pin is low
        humidity = fetch_last_value(HUMIDITY_FEED_KEY)
        temperature = fetch_last_value(TEMPERATURE_FEED_KEY)
        moisture = fetch_last_value(MOISTURE_FEED_KEY)
        current_hour = time.localtime()[3]

        if humidity and temperature and moisture:
            print(f"Humidity: {humidity}, Temperature: {temperature}, Moisture: {moisture}")
            try:
                if (float(humidity) < 50 and float(moisture) < 500 and 
                    float(temperature) > 5): #and 6 <= current_hour < 7):
                    print("Activating sprinkler.")
                    sprinkler_pin.value(0)
                else:
                    print("Deactivating sprinkler.")
                    sprinkler_pin.value(1)
            except ValueError:
                print("Error: Could not convert feed values to float.")
        else:
            print("Failed to fetch feed values.")
        time.sleep(30)  # Delay before the next iteration

    print("Stop pin activated. Exiting script.")
    sprinkler_pin.value(0)  # Ensure the sprinkler is off when exiting

if __name__ == "__main__":
    main()

Phase 3C - Final Code

Python
import network
import socket
import json
import time
import machine
import struct

# Constants and Global Variables
WIFI_SSID = 'XXXX'
WIFI_PASSWORD = 'XXXX'
AIO_USERNAME = 'XXXX'
AIO_KEY = 'XXXX'

# Feed keys
HUMIDITY_FEED_KEY = 'lawn-sprinkler.humidity'
TEMPERATURE_FEED_KEY = 'lawn-sprinkler.temperature'
MOISTURE_FEED_KEY = 'lawn-sprinkler.moisture'
WEATHER_TODAY_FEED_KEY = 'lawn-sprinkler.weathertoday'  # Feed for today's weather conditions

# Initialize the pins
sprinkler_pin = machine.Pin(17, machine.Pin.OUT)  # Sprinkler control pin, assumes active low
stop_pin = machine.Pin(21, machine.Pin.IN, machine.Pin.PULL_DOWN)  # Stop signal pin

# Function definitions (connect_to_wifi, set_time_from_ntp, http_get, get_last_value_from_response) remain unchanged

# Fetch the last value for a specific Adafruit IO feed
def fetch_last_value(feed_key):
    url = f"http://io.adafruit.com/api/v2/{AIO_USERNAME}/feeds/{feed_key}/data/last"
    response = http_get(url)
    return get_last_value_from_response(response)

# Main function incorporating logic based on the feed values
def main():
    connect_to_wifi()
    set_time_from_ntp()

    print("Script is running. Set GPIO 21 high to stop.")

    while stop_pin.value() == 0:  # Continue as long as the stop pin is low
        weather_today = fetch_last_value(WEATHER_TODAY_FEED_KEY).lower()
        
        # Check if weather conditions include rain, showers, or thunderstorm
        if any(condition in weather_today for condition in ["rain", "showers", "thunderstorm"]):
            print("Bad weather today:", weather_today)
            sprinkler_pin.value(1)  # Assuming active low, 1 deactivates the sprinkler
            time.sleep(30)  # Wait before checking again
            continue

        # Proceed with checking other environmental conditions
        humidity = fetch_last_value(HUMIDITY_FEED_KEY)
        temperature = fetch_last_value(TEMPERATURE_FEED_KEY)
        moisture = fetch_last_value(MOISTURE_FEED_KEY)
        current_hour = time.localtime()[3]

        if humidity and temperature and moisture:
            print(f"Humidity: {humidity}, Temperature: {temperature}, Moisture: {moisture}")
            try:
                if (float(humidity) < 50 and float(moisture) < 500 and 
                    float(temperature) > 5) and 6 <= current_hour < 7):
                    print("Activating sprinkler.")
                    sprinkler_pin.value(0)  # Active low, 0 activates the sprinkler
                else:
                    print("Deactivating sprinkler.")
                    sprinkler_pin.value(1)  # Deactivate sprinkler
            except ValueError:
                print("Error: Could not convert feed values to float.")
        else:
            print("Failed to fetch feed values.")
        time.sleep(30)  # Delay before the next iteration

    print("Stop pin activated. Exiting script.")
    sprinkler_pin.value(1)  # Ensure the sprinkler is off when exiting

if __name__ == "__main__":
    main()

Credits

Alex Merchen

Alex Merchen

23 projects • 39 followers
I'm an EE with a Masters in ECE. I like building things.

Comments