Solar Radio check-in

  1. Accomplished this past week
    1. Implemented USB-C circuit that can read voltage level of Voltaic battery
    2. Discovered issue with Arduino timing out was not battery related but meant that we needed to pay $7 for Shiftr
    3. Linked Google Drive to Raspberry Pi via Google Cloud Console and Pi CLI
    4. Developed python script for Pi to run on startup
    5. Figured out how to wire two solar panels in parallel together
    6. Discussed website design/experience
    7. Ordered parts for antenna after revising design
  2. Next Steps

Accomplished this past week

Implemented USB-C circuit that can read voltage level of Voltaic battery

Linked Google Drive to Raspberry Pi via Google Cloud Console and Pi CLI

–checked out a Pi from the shop

–followed this really helpful article about mounting google drive to a Raspberry Pi: https://medium.com/@artur.klauser/mounting-google-drive-on-raspberry-pi-dd15193d8138

–Also consulted the rclone documentation on Google Drive/Linux: https://rclone.org/drive/

–found that a Pi 5 takes 3 minutes or so to upload a 281MB .wav file to Google Drive. If that uses 3 watts at peak based on previous measurements and the battery has 48 watthours, we will need our panels hooked together to ensure the Pi works smoothly.

Developed and cemented code responsible for Arduino (C++) and Pi (python) to communicate with MQTT -> Serial (with aid from Claude.ai)

Arduino code:

#include <WiFiNINA.h>
#include <ArduinoMqttClient.h>
#include "arduino_secrets.h"

// MQTT setup
WiFiClient wifi;
MqttClient mqttClient(wifi);

// MQTT connection details
char broker[] = "energyctrlserver.cloud.shiftr.io";
int port = 1883;
char topic[] = "status";
String clientID = "Arduino";

// Pi communication state
bool piReady = false;

void setup() {
  pinMode(12, OUTPUT);  // For Pi power control
  
  // Initialize serial communications
  Serial.begin(9600);   // Debug serial
  Serial1.begin(9600);  // Serial for Pi communication
  
  // Wait for serial monitor to open
  if (!Serial) delay(3000);
  
  Serial.println("Starting...");
  
  // Connect to WiFi
  connectToNetwork();
  
  // Make the clientID unique with MAC address
  byte mac[6];
  WiFi.macAddress(mac);
  for (int i = 0; i < 3; i++) {
    clientID += String(mac[i], HEX);
  }
  
  // Set MQTT credentials
  mqttClient.setId(clientID);
  mqttClient.setUsernamePassword(SECRET_MQTT_USER, SECRET_MQTT_PASS);
  
  // Clear any data in the serial buffer
  while (Serial1.available()) {
    Serial1.read();
  }
  
  Serial.println("Setup complete. Waiting for Pi to become ready...");
  publishStatus("Arduino initialized. Waiting for Pi...");
}

void loop() {
  // Check WiFi connection
  if (WiFi.status() != WL_CONNECTED) {
    connectToNetwork();
    return;
  }

  // Check MQTT broker connection
  if (!mqttClient.connected()) {
    Serial.println("Attempting to connect to broker");
    connectToBroker();
  }
  
  // Poll for new MQTT messages
  mqttClient.poll();
  
  // Check for Pi messages
  checkPiMessages();
  
  // Process MQTT commands only if Pi is ready
  // Otherwise, we're in initialization or waiting state
  
  delay(10);  // Small delay to prevent busy-wait
}

void checkPiMessages() {
  if (Serial1.available() > 0) {
    String message = Serial1.readStringUntil('\n');
    message.trim();  // Remove any whitespace, CR, LF
    
    Serial.print("Received from Pi: [");
    Serial.print(message);
    Serial.println("]");
    
    // Check for Pi ready message if not already confirmed
    if (!piReady && message.indexOf("Pi ready") >= 0) {
      piReady = true;
      Serial.println("Pi is now ready for communication!");
      publishStatus("Pi is ready for communication");
    }
    
    // Process Pi response
    if (piReady) {
      // Report the message via MQTT
      publishStatus("Pi message: " + message);
      
      // Parse response for specific commands if needed
      if (message.indexOf("103_ACK") >= 0) {
        // This is a response to status request
        publishStatus("Pi status received: " + message);
      } else if (message.indexOf("104_ACK") >= 0) {
        // This is a response to shutdown command
        publishStatus("Pi shutdown acknowledged: " + message);
      }
    }
  }
}

void publishStatus(String message) {
  if (mqttClient.connected()) {
    mqttClient.beginMessage(topic);
    mqttClient.print(message);
    mqttClient.endMessage();
    Serial.println("Published: " + message);
  }
}

void connectToNetwork() {
  Serial.println("Connecting to WiFi...");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.println("Attempting to connect to: " + String(SECRET_SSID));
    WiFi.begin(SECRET_SSID, SECRET_PASS);
    delay(5000);
  }

  Serial.print("Connected. IP address: ");
  Serial.println(WiFi.localIP());
  digitalWrite(LED_BUILTIN, HIGH);
}

boolean connectToBroker() {
  if (!mqttClient.connect(broker, port)) {
    Serial.print("MQTT connection failed. Error: ");
    Serial.println(mqttClient.connectError());
    return false;
  }

  mqttClient.onMessage(onMqttMessage);
  Serial.print("Subscribing to topic: ");
  Serial.println(topic);
  mqttClient.subscribe(topic);
  
  // Send connection status
  publishStatus("Arduino connected to MQTT");
  
  return true;
}

void onMqttMessage(int messageSize) {
  // Read the message
  String incoming = "";
  while (mqttClient.available()) {
    incoming += (char)mqttClient.read();
  }
  
  Serial.print("MQTT received: ");
  Serial.println(incoming);

   // Only process numeric codes
    int code = incoming.toInt();
    
    if (code == 101) {
      reply101();
    } 
    if (code == 102) {
      turnonpi();
    }
  
  // Process commands only if Pi is ready
  if (piReady) {
   
    if (code == 103) {
      statuspi();
    }
    else if (code == 104) {
      shutdownpi();
    }
    else if (code > 0) {
      // Forward code to Pi
      sendToPi(incoming);
    }
  } 
  //else {
  //   // Pi not ready yet
  //   publishStatus("Cannot process command, Pi not ready");
  // }
}

void reply101() {
  publishStatus("301");
  Serial.println("Status reported: 301");
}

void turnonpi() {
  Serial.println("Turning on Pi");
  digitalWrite(12, HIGH);
  publishStatus("Pi Booting");
  
  // Reset Pi ready flag - we'll wait for new ready signal
  piReady = false;
}

void statuspi() {
  Serial.println("Requesting Pi status");
  sendToPi("103");
}

void shutdownpi() {
  Serial.println("Sending shutdown command to Pi");
  sendToPi("104");
  
  // Reset Pi ready flag since it's shutting down
  piReady = false;
  publishStatus("Pi shutting down, communication disabled");
}

void sendToPi(String command) {
  // Only send if Pi is ready
  if (piReady) {
    Serial.print("Sending to Pi: ");
    Serial.println(command);
    
    // Add newline as command terminator
    Serial1.print(command);
    Serial1.print('\n');
    Serial1.flush();  // Ensure transmission completes
    
    publishStatus("Command sent to Pi: " + command);
  } else {
    Serial.println("Cannot send command, Pi not ready");
    publishStatus("Command failed, Pi not ready");
  }
}

Pi Code:

#!/usr/bin/env python3
import serial
import time
import subprocess
import logging
import os
import signal
import sys

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("/home/pi/serial_handler.log"),
        logging.StreamHandler()
    ]
)

# Serial port configuration
SERIAL_PORT = '/dev/serial0'  # Hardware UART on Pi
BAUD_RATE = 9600

# Command dictionary - maps codes to terminal commands and responses
COMMANDS = {
    "103": {
        "command": "uptime", # Get system uptime
        "response": "103_ACK",
        "params_required": 0
    },
    "104": {
        "command": "sudo shutdown -h now",
        "response": "104_ACK",
        "params_required": 0
    },
    # Add more commands as needed
}

def execute_command(cmd):
    """Execute a shell command and return the output"""
    try:
        # Special handling for shutdown command
        if "shutdown" in cmd:
            logging.info(f"Executing shutdown command: {cmd}")
            # Return immediately for shutdown command
            subprocess.Popen(cmd, shell=True)
            return "Shutdown initiated"
        
        # Normal command execution
        result = subprocess.check_output(cmd, shell=True, text=True, timeout=5)
        logging.info(f"Command executed: {cmd}")
        return result.strip()
    except subprocess.TimeoutExpired:
        return "Command timed out"
    except subprocess.CalledProcessError as e:
        logging.error(f"Command failed: {e}")
        return f"Error: {e}"

def signal_handler(sig, frame):
    """Handle SIGTERM and SIGINT for clean shutdown"""
    logging.info("Shutdown signal received, exiting...")
    if 'ser' in globals() and ser.is_open:
        ser.close()
    sys.exit(0)

def main():
    global ser
    
    # Set up signal handlers for clean shutdown
    signal.signal(signal.SIGTERM, signal_handler)
    signal.signal(signal.SIGINT, signal_handler)
    
    try:
        # Open serial connection with explicit settings
        ser = serial.Serial(
            port=SERIAL_PORT,
            baudrate=BAUD_RATE,
            bytesize=serial.EIGHTBITS,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            timeout=1
        )
        
        logging.info(f"Serial connection opened on {SERIAL_PORT} at {BAUD_RATE} baud")
        
        # Clear any initial data
        ser.reset_input_buffer()
        ser.reset_output_buffer()
        
        # Send ready signal to Arduino
        time.sleep(2)  # Short delay to ensure things are settled
        ready_message = "Pi ready\n"
        ser.write(ready_message.encode('ascii'))
        logging.info("Sent ready signal to Arduino")
        
        # Buffer to store incoming data
        buffer = ""
        
        while True:
            # Read data if available
            if ser.in_waiting > 0:
                try:
                    # Read a line (until newline character)
                    line = ser.readline().decode('ascii', errors='replace').strip()
                    
                    if line:  # Only process non-empty lines
                        logging.info(f"Received command: {line}")
                        
                        # Process the command
                        if line in COMMANDS:
                            cmd_info = COMMANDS[line]
                            cmd_output = execute_command(cmd_info["command"])
                            
                            # Send response
                            response = f"{cmd_info['response']}:{cmd_output[:50]}\n"
                            ser.write(response.encode('ascii'))
                            logging.info(f"Sent response: {response.strip()}")
                        else:
                            # Unknown command
                            ser.write(f"UNKNOWN_CODE:{line}\n".encode('ascii'))
                            logging.warning(f"Unknown command received: {line}")
                            
                except Exception as e:
                    logging.error(f"Error processing command: {e}")
            
            # Small delay to prevent CPU hogging
            time.sleep(0.1)
            
    except KeyboardInterrupt:
        logging.info("Program terminated by user")
    except Exception as e:
        logging.error(f"Error: {e}")
    finally:
        if 'ser' in locals() and ser.is_open:
            ser.close()
            logging.info("Serial connection closed")

if __name__ == "__main__":
    main()

Developed python script for Pi to run on startup

.Sh Code:

#!/bin/bash

# Define file path and content
FILE_PATH="/home/meliliyan/Desktop/test_file.txt"
FILE_CONTENT="test hello"
GDRIVE_PATH="gdrive:'nyu classes'" # Assuming gdrive is the name of your rclone remote
LOG_FILE="/home/meliliyan/Desktop/startup_log.txt"

#Create log function
log() {
   echo "$(date): $1" >> "$LOG_FILE"
}

log "Starting startup script execution"


# Step 1: Create a file and write "test" to it
echo "$FILE_CONTENT" > "$FILE_PATH"
log "Created file $FILE_PATH with content '$FILE_CONTENT'"

# Step 2: File is automatically saved when written
log "File saved"

# Step 3: Wait a bit to ensure network is up
log "Waiting 30 seconds for network to be fully ready"
sleep 30
log "Proceeding with rclone copy"

# add full path to rclone to ensure
export PATH=$PATH:/usr/bin:/usr/local/bin
log "PATH set to: $PATH"

CONFIG_FILE="/home/meliliyan/.config/rclone/rclone.conf"
log "Using rclone config: $CONFIG_FILE"

# send file to google drive with error handling
log "Attempting to copy file to Google Drive"

sudo -u meliliyan /usr/bin/rclone copy "$FILE_PATH" "$GDRIVE_PATH" --config="$CONFIG_FILE" --verbose >> "$LOG_FILE" 2>&1
RCLONE_EXIT_CODE=$?

if [ $RCLONE_EXIT_CODE -eq 0 ]; then
	log "rclone copy succeeded with exit code: $RCLONE_EXIT_CODE"
else
	log "rclone copy failed with exit code: $RCLONE_EXIT_CODE"
	
	#diagnostic info
	log "rclone version info"
	/usr/bin/rclone version >> "$LOG_FILE" 2>&1

	log "Network status:"
	ping -c 4 8.8.8.8 >> "$LOG_FILE" 2>&1

	log "Remotes configured:"
	sudo -u meliliyan /usr/bin/rclone --config="$CONFIG_FILE" listremotes >> "$LOG_FILE" 2>&1
fi


# Step 4: Shutdown the Pi
if [ $RCLONE_EXIT_CODE -eq 0 ]; then
	log "all done. shutting down system"
	#/sbin/shutdown -h now
else
	log "Not shutting down to rclone failure"
fi

Figured out how to wire two solar panels in parallel together

Voltage of above: 6.76 Volts

Discussed website design/experience

–do we give users ability to request radio frequencies and images?

–Or just radio frequencies?

–What does the data pipeline look like?

–Potential pivot to SSTV interaction

–a cool example of SSTV encoding/decoding: https://open-weather.community/decode/

Ordered parts for antenna after revising design

Next Steps

  1. Build antenna and install
  2. Install solar panels wired together with battery and voltage reader
  3. Install pi with scripts and google drive link installed
  4. Integrate working cycle of MQTT request -> Arduino -> Pi -> SDR -> Google Drive upload
  5. Flesh out website design and start building with ready data

Also, I will need research and develop my presentation for next week’s class!


Comments

Leave a comment