494 lines
18 KiB
Bash
Executable File
494 lines
18 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -e
|
|
|
|
source .env
|
|
|
|
## Spore Control Script
|
|
## Usage: ./ctl.sh <command> [options]
|
|
##
|
|
## Commands:
|
|
## build [target] - Build firmware for target (base, d1_mini, etc.)
|
|
## flash [target] - Flash firmware to device
|
|
## uploadfs [target] - Upload filesystem to device
|
|
## ota update <ip> <target> - OTA update specific node
|
|
## ota all <target> - OTA update all nodes in cluster
|
|
## cluster members - List cluster members
|
|
## node wifi <ssid> <password> [ip] - Configure WiFi on node
|
|
## node label set <key=value> [ip] - Set a label on node
|
|
## node label delete <key> [ip] - Delete a label from node
|
|
## node config get [ip] - Get node configuration
|
|
## node status [ip] - Get node status and information
|
|
## monitor - Monitor serial output
|
|
##
|
|
## Examples:
|
|
## ./ctl.sh build base
|
|
## ./ctl.sh flash d1_mini
|
|
## ./ctl.sh node wifi "MyNetwork" "MyPassword"
|
|
## ./ctl.sh node wifi "MyNetwork" "MyPassword" 192.168.1.100
|
|
## ./ctl.sh node label set "environment=production"
|
|
## ./ctl.sh node label set "location=office" 192.168.1.100
|
|
## ./ctl.sh node label delete "environment"
|
|
## ./ctl.sh node config get
|
|
## ./ctl.sh node config get 192.168.1.100
|
|
## ./ctl.sh node status
|
|
## ./ctl.sh node status 192.168.1.100
|
|
|
|
function info {
|
|
sed -n 's/^##//p' ctl.sh
|
|
}
|
|
|
|
function build {
|
|
function target {
|
|
echo "Building project for $1..."
|
|
pio run -e $1
|
|
}
|
|
function all {
|
|
pio run
|
|
}
|
|
${@:-all}
|
|
}
|
|
|
|
function flash {
|
|
function target {
|
|
echo "Flashing firmware for $1..."
|
|
pio run --target upload -e $1
|
|
}
|
|
${@:-info}
|
|
}
|
|
|
|
function mkfs {
|
|
~/bin/mklittlefs -c data \
|
|
-s 0x9000 \
|
|
-b 4096 \
|
|
-p 256 \
|
|
littlefs.bin
|
|
}
|
|
|
|
function flashfs {
|
|
esptool.py --port /dev/ttyUSB0 \
|
|
--baud 115200 \
|
|
write_flash 0xbb000 littlefs.bin
|
|
}
|
|
|
|
function uploadfs {
|
|
echo "Uploading files to LittleFS..."
|
|
pio run -e $1 -t uploadfs
|
|
}
|
|
|
|
function ota {
|
|
function update {
|
|
echo "Updating node at $1 with $2... "
|
|
curl -X POST \
|
|
-F "file=@.pio/build/$2/firmware.bin" \
|
|
http://$1/api/node/update | jq -r '.status'
|
|
}
|
|
function all {
|
|
echo "Updating all nodes..."
|
|
curl -s http://$API_NODE/api/cluster/members | jq -r '.members.[].ip' | while read -r ip; do
|
|
ota update $ip $1
|
|
done
|
|
}
|
|
${@:-info}
|
|
}
|
|
|
|
function cluster {
|
|
function members {
|
|
curl -s http://$API_NODE/api/cluster/members | jq -r '.members[] | "\(.hostname) \(.ip)"'
|
|
}
|
|
${@:-info}
|
|
}
|
|
|
|
function node {
|
|
function wifi {
|
|
if [ $# -lt 2 ]; then
|
|
echo "Usage: $0 node wifi <ssid> <password> [node_ip]"
|
|
echo " ssid: WiFi network name"
|
|
echo " password: WiFi password"
|
|
echo " node_ip: Optional IP address (defaults to API_NODE from .env)"
|
|
return 1
|
|
fi
|
|
|
|
local ssid="$1"
|
|
local password="$2"
|
|
local node_ip="${3:-$API_NODE}"
|
|
|
|
echo "Configuring WiFi on node $node_ip..."
|
|
echo "SSID: $ssid"
|
|
|
|
# Configure WiFi using the API endpoint
|
|
response=$(curl -s -w "\n%{http_code}" -X POST \
|
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
-d "ssid=$ssid&password=$password" \
|
|
"http://$node_ip/api/network/wifi/config" 2>/dev/null || echo -e "\n000")
|
|
|
|
# Extract HTTP status code and response body
|
|
http_code=$(echo "$response" | tail -n1)
|
|
response_body=$(echo "$response" | head -n -1)
|
|
|
|
# Check if curl succeeded
|
|
if [ "$http_code" = "000" ] || [ -z "$response_body" ]; then
|
|
echo "Error: Failed to connect to node at $node_ip"
|
|
echo "Please check:"
|
|
echo " - Node is powered on and connected to network"
|
|
echo " - IP address is correct"
|
|
echo " - Node is running Spore firmware"
|
|
return 1
|
|
fi
|
|
|
|
# Check HTTP status code
|
|
if [ "$http_code" != "200" ]; then
|
|
echo "Error: HTTP $http_code - Server error"
|
|
echo "Response: $response_body"
|
|
return 1
|
|
fi
|
|
|
|
# Parse and display the response
|
|
status=$(echo "$response_body" | jq -r '.status // "unknown"')
|
|
message=$(echo "$response_body" | jq -r '.message // "No message"')
|
|
config_saved=$(echo "$response_body" | jq -r '.config_saved // false')
|
|
restarting=$(echo "$response_body" | jq -r '.restarting // false')
|
|
connected=$(echo "$response_body" | jq -r '.connected // false')
|
|
ip=$(echo "$response_body" | jq -r '.ip // "N/A"')
|
|
|
|
echo "Status: $status"
|
|
echo "Message: $message"
|
|
echo "Config saved: $config_saved"
|
|
if [ "$restarting" = "true" ]; then
|
|
echo "Restarting: true"
|
|
echo "Note: Node will restart to apply new WiFi settings"
|
|
fi
|
|
echo "Connected: $connected"
|
|
if [ "$connected" = "true" ]; then
|
|
echo "IP Address: $ip"
|
|
fi
|
|
|
|
# Return appropriate exit code
|
|
if [ "$status" = "success" ]; then
|
|
echo "WiFi configuration completed successfully!"
|
|
return 0
|
|
else
|
|
echo "WiFi configuration failed!"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function label {
|
|
function set {
|
|
if [ $# -lt 1 ]; then
|
|
echo "Usage: $0 node label set <key=value> [node_ip]"
|
|
echo " key=value: Label key and value in format 'key=value'"
|
|
echo " node_ip: Optional IP address (defaults to API_NODE from .env)"
|
|
return 1
|
|
fi
|
|
|
|
local key_value="$1"
|
|
local node_ip="${2:-$API_NODE}"
|
|
|
|
# Parse key=value format
|
|
if [[ ! "$key_value" =~ ^[^=]+=.+$ ]]; then
|
|
echo "Error: Label must be in format 'key=value'"
|
|
echo "Example: environment=production"
|
|
return 1
|
|
fi
|
|
|
|
local key="${key_value%%=*}"
|
|
local value="${key_value#*=}"
|
|
|
|
echo "Setting label '$key=$value' on node $node_ip..."
|
|
|
|
# First get current labels
|
|
current_labels=$(curl -s "http://$node_ip/api/node/status" | jq -r '.labels // {}')
|
|
|
|
# Add/update the new label
|
|
updated_labels=$(echo "$current_labels" | jq --arg key "$key" --arg value "$value" '. + {($key): $value}')
|
|
|
|
# Send updated labels to the node
|
|
response=$(curl -s -w "\n%{http_code}" -X POST \
|
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
-d "labels=$updated_labels" \
|
|
"http://$node_ip/api/node/config" 2>/dev/null || echo -e "\n000")
|
|
|
|
# Extract HTTP status code and response body
|
|
http_code=$(echo "$response" | tail -n1)
|
|
response_body=$(echo "$response" | head -n -1)
|
|
|
|
# Check if curl succeeded
|
|
if [ "$http_code" = "000" ] || [ -z "$response_body" ]; then
|
|
echo "Error: Failed to connect to node at $node_ip"
|
|
echo "Please check:"
|
|
echo " - Node is powered on and connected to network"
|
|
echo " - IP address is correct"
|
|
echo " - Node is running Spore firmware"
|
|
return 1
|
|
fi
|
|
|
|
# Check HTTP status code
|
|
if [ "$http_code" != "200" ]; then
|
|
echo "Error: HTTP $http_code - Server error"
|
|
echo "Response: $response_body"
|
|
return 1
|
|
fi
|
|
|
|
# Parse and display the response
|
|
status=$(echo "$response_body" | jq -r '.status // "unknown"')
|
|
message=$(echo "$response_body" | jq -r '.message // "No message"')
|
|
|
|
echo "Status: $status"
|
|
echo "Message: $message"
|
|
|
|
# Return appropriate exit code
|
|
if [ "$status" = "success" ]; then
|
|
echo "Label '$key=$value' set successfully!"
|
|
return 0
|
|
else
|
|
echo "Failed to set label!"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function delete {
|
|
if [ $# -lt 1 ]; then
|
|
echo "Usage: $0 node label delete <key> [node_ip]"
|
|
echo " key: Label key to delete"
|
|
echo " node_ip: Optional IP address (defaults to API_NODE from .env)"
|
|
return 1
|
|
fi
|
|
|
|
local key="$1"
|
|
local node_ip="${2:-$API_NODE}"
|
|
|
|
echo "Deleting label '$key' from node $node_ip..."
|
|
|
|
# First get current labels
|
|
current_labels=$(curl -s "http://$node_ip/api/node/status" | jq -r '.labels // {}')
|
|
|
|
# Check if key exists
|
|
if [ "$(echo "$current_labels" | jq -r --arg key "$key" 'has($key)')" != "true" ]; then
|
|
echo "Warning: Label '$key' does not exist on node"
|
|
return 0
|
|
fi
|
|
|
|
# Remove the key
|
|
updated_labels=$(echo "$current_labels" | jq --arg key "$key" 'del(.[$key])')
|
|
|
|
# Send updated labels to the node
|
|
response=$(curl -s -w "\n%{http_code}" -X POST \
|
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
-d "labels=$updated_labels" \
|
|
"http://$node_ip/api/node/config" 2>/dev/null || echo -e "\n000")
|
|
|
|
# Extract HTTP status code and response body
|
|
http_code=$(echo "$response" | tail -n1)
|
|
response_body=$(echo "$response" | head -n -1)
|
|
|
|
# Check if curl succeeded
|
|
if [ "$http_code" = "000" ] || [ -z "$response_body" ]; then
|
|
echo "Error: Failed to connect to node at $node_ip"
|
|
echo "Please check:"
|
|
echo " - Node is powered on and connected to network"
|
|
echo " - IP address is correct"
|
|
echo " - Node is running Spore firmware"
|
|
return 1
|
|
fi
|
|
|
|
# Check HTTP status code
|
|
if [ "$http_code" != "200" ]; then
|
|
echo "Error: HTTP $http_code - Server error"
|
|
echo "Response: $response_body"
|
|
return 1
|
|
fi
|
|
|
|
# Parse and display the response
|
|
status=$(echo "$response_body" | jq -r '.status // "unknown"')
|
|
message=$(echo "$response_body" | jq -r '.message // "No message"')
|
|
|
|
echo "Status: $status"
|
|
echo "Message: $message"
|
|
|
|
# Return appropriate exit code
|
|
if [ "$status" = "success" ]; then
|
|
echo "Label '$key' deleted successfully!"
|
|
return 0
|
|
else
|
|
echo "Failed to delete label!"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
${@:-info}
|
|
}
|
|
|
|
function config {
|
|
function get {
|
|
local node_ip="${1:-$API_NODE}"
|
|
|
|
echo "Getting configuration for node $node_ip..."
|
|
|
|
# Get node configuration
|
|
response=$(curl -s -w "\n%{http_code}" "http://$node_ip/api/node/config" 2>/dev/null || echo -e "\n000")
|
|
|
|
# Extract HTTP status code and response body
|
|
http_code=$(echo "$response" | tail -n1)
|
|
response_body=$(echo "$response" | head -n -1)
|
|
|
|
# Check if curl succeeded
|
|
if [ "$http_code" = "000" ] || [ -z "$response_body" ]; then
|
|
echo "Error: Failed to connect to node at $node_ip"
|
|
echo "Please check:"
|
|
echo " - Node is powered on and connected to network"
|
|
echo " - IP address is correct"
|
|
echo " - Node is running Spore firmware"
|
|
return 1
|
|
fi
|
|
|
|
# Check HTTP status code
|
|
if [ "$http_code" != "200" ]; then
|
|
echo "Error: HTTP $http_code - Server error"
|
|
echo "Response: $response_body"
|
|
return 1
|
|
fi
|
|
|
|
# Parse and display the response in a nice format
|
|
echo ""
|
|
echo "=== Node Configuration ==="
|
|
echo "Node IP: $node_ip"
|
|
echo "Retrieved at: $(date)"
|
|
echo ""
|
|
|
|
# WiFi Configuration
|
|
echo "=== WiFi Configuration ==="
|
|
echo "SSID: $(echo "$response_body" | jq -r '.wifi.ssid // "N/A"')"
|
|
echo "Connect Timeout: $(echo "$response_body" | jq -r '.wifi.connect_timeout_ms // "N/A"') ms"
|
|
echo "Retry Delay: $(echo "$response_body" | jq -r '.wifi.retry_delay_ms // "N/A"') ms"
|
|
echo "Password: [HIDDEN]"
|
|
echo ""
|
|
|
|
# Network Configuration
|
|
echo "=== Network Configuration ==="
|
|
echo "UDP Port: $(echo "$response_body" | jq -r '.network.udp_port // "N/A"')"
|
|
echo "API Server Port: $(echo "$response_body" | jq -r '.network.api_server_port // "N/A"')"
|
|
echo ""
|
|
|
|
# Cluster Configuration
|
|
echo "=== Cluster Configuration ==="
|
|
echo "Heartbeat Interval: $(echo "$response_body" | jq -r '.cluster.heartbeat_interval_ms // "N/A"') ms"
|
|
echo "Cluster Listen Interval: $(echo "$response_body" | jq -r '.cluster.cluster_listen_interval_ms // "N/A"') ms"
|
|
echo "Status Update Interval: $(echo "$response_body" | jq -r '.cluster.status_update_interval_ms // "N/A"') ms"
|
|
echo ""
|
|
|
|
# Node Status Thresholds
|
|
echo "=== Node Status Thresholds ==="
|
|
echo "Active Threshold: $(echo "$response_body" | jq -r '.thresholds.node_active_threshold_ms // "N/A"') ms"
|
|
echo "Inactive Threshold: $(echo "$response_body" | jq -r '.thresholds.node_inactive_threshold_ms // "N/A"') ms"
|
|
echo "Dead Threshold: $(echo "$response_body" | jq -r '.thresholds.node_dead_threshold_ms // "N/A"') ms"
|
|
echo ""
|
|
|
|
# System Configuration
|
|
echo "=== System Configuration ==="
|
|
echo "Restart Delay: $(echo "$response_body" | jq -r '.system.restart_delay_ms // "N/A"') ms"
|
|
echo "JSON Doc Size: $(echo "$response_body" | jq -r '.system.json_doc_size // "N/A"') bytes"
|
|
echo ""
|
|
|
|
# Memory Management
|
|
echo "=== Memory Management ==="
|
|
echo "Low Memory Threshold: $(echo "$response_body" | jq -r '.memory.low_memory_threshold_bytes // "N/A"') bytes"
|
|
echo "Critical Memory Threshold: $(echo "$response_body" | jq -r '.memory.critical_memory_threshold_bytes // "N/A"') bytes"
|
|
echo "Max Concurrent HTTP Requests: $(echo "$response_body" | jq -r '.memory.max_concurrent_http_requests // "N/A"')"
|
|
echo ""
|
|
|
|
# Custom Labels
|
|
labels=$(echo "$response_body" | jq -r '.labels // {}')
|
|
if [ "$labels" != "{}" ] && [ "$labels" != "null" ]; then
|
|
echo "=== Custom Labels ==="
|
|
echo "$labels" | jq -r 'to_entries[] | "\(.key): \(.value)"'
|
|
echo ""
|
|
else
|
|
echo "=== Custom Labels ==="
|
|
echo "No custom labels set"
|
|
echo ""
|
|
fi
|
|
|
|
# Metadata
|
|
echo "=== Metadata ==="
|
|
echo "Configuration Version: $(echo "$response_body" | jq -r '.version // "N/A"')"
|
|
echo "Retrieved Timestamp: $(echo "$response_body" | jq -r '.retrieved_at // "N/A"')"
|
|
echo ""
|
|
|
|
echo "=== Raw JSON Response ==="
|
|
echo "$response_body" | jq '.'
|
|
|
|
return 0
|
|
}
|
|
|
|
${@:-info}
|
|
}
|
|
|
|
function status {
|
|
local node_ip="${1:-$API_NODE}"
|
|
|
|
echo "Getting status for node $node_ip..."
|
|
|
|
# Get node status
|
|
response=$(curl -s -w "\n%{http_code}" "http://$node_ip/api/node/status" 2>/dev/null || echo -e "\n000")
|
|
|
|
# Extract HTTP status code and response body
|
|
http_code=$(echo "$response" | tail -n1)
|
|
response_body=$(echo "$response" | head -n -1)
|
|
|
|
# Check if curl succeeded
|
|
if [ "$http_code" = "000" ] || [ -z "$response_body" ]; then
|
|
echo "Error: Failed to connect to node at $node_ip"
|
|
echo "Please check:"
|
|
echo " - Node is powered on and connected to network"
|
|
echo " - IP address is correct"
|
|
echo " - Node is running Spore firmware"
|
|
return 1
|
|
fi
|
|
|
|
# Check HTTP status code
|
|
if [ "$http_code" != "200" ]; then
|
|
echo "Error: HTTP $http_code - Server error"
|
|
echo "Response: $response_body"
|
|
return 1
|
|
fi
|
|
|
|
# Parse and display the response in a nice format
|
|
echo ""
|
|
echo "=== Node Status ==="
|
|
echo "Hostname: $(echo "$response_body" | jq -r '.hostname // "N/A"')"
|
|
echo "IP Address: $node_ip"
|
|
echo "Free Heap: $(echo "$response_body" | jq -r '.freeHeap // "N/A"') bytes"
|
|
echo "Chip ID: $(echo "$response_body" | jq -r '.chipId // "N/A"')"
|
|
echo "SDK Version: $(echo "$response_body" | jq -r '.sdkVersion // "N/A"')"
|
|
echo "CPU Frequency: $(echo "$response_body" | jq -r '.cpuFreqMHz // "N/A"') MHz"
|
|
echo "Flash Size: $(echo "$response_body" | jq -r '.flashChipSize // "N/A"') bytes"
|
|
|
|
# Display labels if present
|
|
labels=$(echo "$response_body" | jq -r '.labels // {}')
|
|
if [ "$labels" != "{}" ] && [ "$labels" != "null" ]; then
|
|
echo ""
|
|
echo "=== Labels ==="
|
|
echo "$labels" | jq -r 'to_entries[] | "\(.key): \(.value)"'
|
|
else
|
|
echo ""
|
|
echo "=== Labels ==="
|
|
echo "No labels set"
|
|
fi
|
|
|
|
echo ""
|
|
echo "=== Raw JSON Response ==="
|
|
echo "$response_body" | jq '.'
|
|
|
|
return 0
|
|
}
|
|
|
|
${@:-info}
|
|
}
|
|
|
|
function monitor {
|
|
pio run --target monitor
|
|
}
|
|
|
|
${@:-info}
|