Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,690 @@
#!/usr/bin/env bash
set -e
# Default values
EVEREST_TOOL_BRANCH="main"
# Script directory - where devrd is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# .devcontainer directory is always relative to the script location
DEVCONTAINER_DIR="${SCRIPT_DIR}/../../.devcontainer"
# .env file is always in the .devcontainer directory (relative to script)
ENV_FILE="${DEVCONTAINER_DIR}/.env"
# Function to load HOST_WORKSPACE_FOLDER from .env file
# Usage: load_workspace_from_env [fallback]
# If fallback is provided and workspace not found in .env, returns fallback
# If no fallback provided, returns empty string (for use with ${var:-default} syntax)
load_workspace_from_env() {
local fallback="$1"
if [ -f "$ENV_FILE" ]; then
local workspace=$(grep "^HOST_WORKSPACE_FOLDER=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
if [ -n "$workspace" ]; then
echo "$workspace"
return
fi
fi
# If fallback provided and workspace not found, return fallback
if [ -n "$fallback" ]; then
echo "$fallback"
fi
}
# HOST_WORKSPACE_FOLDER is the folder that is mapped to /workspace in the container
# Priority: 1) Command line/env var, 2) .env file, 3) Current directory
HOST_WORKSPACE_FOLDER="${HOST_WORKSPACE_FOLDER:-$(load_workspace_from_env)}"
HOST_WORKSPACE_FOLDER="${HOST_WORKSPACE_FOLDER:-$(pwd)}"
# Docker Compose project name (defaults to workspace folder name with _devcontainer suffix, can be overridden)
# This matches VSC's naming convention: {workspace-folder-name}_devcontainer-{service-name}-1
# If needed (and not running in VSCode), can be changed by setting the DOCKER_COMPOSE_PROJECT_NAME environment variable.
DOCKER_COMPOSE_PROJECT_NAME="${DOCKER_COMPOSE_PROJECT_NAME:-$(basename "$HOST_WORKSPACE_FOLDER" | tr \"A-Z\" \"a-z\")_devcontainer}"
# Function to detect if running inside container
is_inside_container() {
# Check for /.dockerenv (standard Docker indicator)
[ -f /.dockerenv ] && return 0
# Check if /workspace exists and is mounted (devcontainer specific)
[ -d /workspace ] && [ -f /workspace/.devcontainer/devrd ] && return 0
return 1
}
# Function to show error when command is run from inside container
show_inside_container_error() {
local cmd_name="${1:-this command}"
echo "✖ Error: This command cannot be run from inside the container"
echo ""
echo "You are currently inside the development container."
echo "Please run this command from the host system instead:"
echo ""
echo " 1. Exit the container (type 'exit' or press Ctrl+D)"
echo " 2. Run the command from your host terminal:"
echo " ./devrd $cmd_name"
echo ""
exit 1
}
# Function to run docker compose with static project name
# Compose files are always relative to the script's .devcontainer directory
docker_compose() {
docker compose -p "$DOCKER_COMPOSE_PROJECT_NAME" \
-f "${DEVCONTAINER_DIR}/docker-compose.yml" \
-f "${DEVCONTAINER_DIR}/general-devcontainer/docker-compose.devcontainer.yml" "$@"
}
# Function to validate folder path
validate_folder() {
local folder="$1"
# Convert relative path to absolute
case "$folder" in
/*) ;; # Already absolute
*) folder="$(cd "$folder" && pwd)" ;; # Convert relative to absolute
esac
# Check if folder exists
if [ ! -d "$folder" ]; then
echo "Error: Folder '$folder' does not exist"
exit 1
fi
# Check if folder is readable
if [ ! -r "$folder" ]; then
echo "Error: Folder '$folder' is not accessible (permission denied)"
exit 1
fi
echo "$folder"
}
# Function to generate .env file
generate_env() {
if is_inside_container; then
show_inside_container_error "env"
fi
# Process command line options
if [ -n "$ENV_OPTIONS" ]; then
set -- $ENV_OPTIONS
while [ $# -gt 0 ]; do
case "$1" in
-v|--version)
EVEREST_TOOL_BRANCH="$2"
shift 2
;;
-w|--workspace)
HOST_WORKSPACE_FOLDER="$2"
shift 2
;;
*)
shift
;;
esac
done
fi
# Set workspace folder
if [ -n "$HOST_WORKSPACE_FOLDER" ]; then
HOST_WORKSPACE_FOLDER=$(validate_folder "$HOST_WORKSPACE_FOLDER")
else
HOST_WORKSPACE_FOLDER="$(pwd)"
fi
# Get commit hash
COMMIT_HASH=$(git ls-remote https://github.com/EVerest/everest-dev-environment.git ${EVEREST_TOOL_BRANCH} | cut -f1 2>/dev/null || echo "")
# Check if we need to update existing file
local needs_update=false
if [ -f "$ENV_FILE" ] && [ -s "$ENV_FILE" ]; then
# File exists, check if we have options that require updates
if [ -n "$ENV_OPTIONS" ]; then
needs_update=true
fi
fi
if [ ! -f "$ENV_FILE" ] || [ ! -s "$ENV_FILE" ] || [ "$needs_update" = true ]; then
cat > "$ENV_FILE" << EOF
# Auto-generated by devrd script
ORGANIZATION_ARG=EVerest
REPOSITORY_HOST=github.com
REPOSITORY_USER=git
COMMIT_HASH=$COMMIT_HASH
EVEREST_TOOL_BRANCH=$EVEREST_TOOL_BRANCH
UID=$(id -u)
GID=$(id -g)
HOST_WORKSPACE_FOLDER=$HOST_WORKSPACE_FOLDER
EOF
if [ "$needs_update" = true ]; then
echo "Updated .env file"
else
echo "Generated .env file"
fi
else
echo "Found existing .env file"
cat "$ENV_FILE"
fi
}
# Function to build the container
build_container() {
if is_inside_container; then
show_inside_container_error "build"
fi
echo "Building development container..."
docker_compose --profile all build
}
# Function to get actual port mapping from docker compose
get_port_mapping() {
local service_name=$1
local internal_port=$2
# Get the actual port mapping from docker compose
local port_mapping=$(docker_compose port $service_name $internal_port 2>/dev/null)
if [ -n "$port_mapping" ]; then
# Extract just the host port (remove the host part)
echo "$port_mapping" | sed 's/.*://'
else
echo ""
fi
}
# Function to display container links and tips
display_container_status() {
echo ""
echo "Container Services Summary:"
echo "=============================="
# Get actual port mappings from docker compose
local mqtt_explorer_port=$(get_port_mapping mqtt-explorer 4000)
local steve_http_port=$(get_port_mapping steve 8180)
# Display links with actual ports
if [ -n "$mqtt_explorer_port" ]; then
echo "MQTT Explorer: http://localhost:$mqtt_explorer_port"
else
echo "MQTT Explorer: currently not running"
fi
if [ -n "$steve_http_port" ]; then
echo "Steve (HTTP): http://localhost:$steve_http_port"
else
echo "Steve (HTTP): currently not running"
fi
# Check if Node-RED is running
if docker_compose ps | grep -q "nodered"; then
echo "Node-RED UI: http://localhost:1880/ui"
else
echo "Node-RED UI: currently not running"
fi
echo ""
echo "Tips:"
echo " • MQTT Explorer: Browse and debug MQTT topics"
echo " • Steve: OCPP backend management interface"
echo " • Node-RED: Web-based UI for SIL simulations"
echo " • Use './devrd prompt' to access the container shell"
echo " • Use './devrd nodered-flows' to see available flows"
echo ""
}
# Function to start containers using profiles
start_compose_profile() {
if is_inside_container; then
show_inside_container_error "start"
fi
local profile_or_service="$1"
if [ -n "$profile_or_service" ]; then
echo "Starting containers for profile/service: $profile_or_service..."
docker_compose --profile "$profile_or_service" up -d
else
echo "Starting the development container and all services..."
docker_compose --profile all up -d
fi
# Display workspace mapping
echo "Workspace mapping: $HOST_WORKSPACE_FOLDER → /workspace"
echo ""
# Display container links
display_container_status
}
# Function to stop containers using profiles or container name pattern
stop_compose_profile() {
if is_inside_container; then
show_inside_container_error "stop"
fi
local profile_or_pattern="$1"
if [ -n "$profile_or_pattern" ]; then
# Check if it's a valid profile name
case "$profile_or_pattern" in
mqtt|ocpp|sil|all)
echo "Stopping containers for profile: $profile_or_pattern..."
docker_compose --profile "$profile_or_pattern" stop
;;
*)
# Treat as container name pattern
echo "Stopping containers matching pattern: $profile_or_pattern..."
local containers=$(docker ps --format "{{.Names}}" | grep -E "($profile_or_pattern)" || true)
if [ -z "$containers" ]; then
echo "No running containers found matching pattern: $profile_or_pattern"
return 1
fi
echo "$containers" | while read container; do
echo "Stopping container: $container"
docker stop "$container" 2>/dev/null || echo "Failed to stop container: $container"
done
;;
esac
else
echo "Stopping the development container and all services..."
docker_compose --profile all stop
fi
}
# Function to purge everything
purge_everything() {
if is_inside_container; then
show_inside_container_error "purge"
fi
local purge_pattern="${1:-$(basename "$HOST_WORKSPACE_FOLDER")}"
local current_project="$(basename "$HOST_WORKSPACE_FOLDER")"
echo "Purging all devcontainer resources for pattern: $purge_pattern..."
# Only use docker_compose down if purging the current project
if [ "$purge_pattern" = "$current_project" ]; then
echo "Stopping and removing containers for current project..."
docker_compose down -v --remove-orphans
else
echo "Purging resources for different project pattern: $purge_pattern"
echo "Skipping docker-compose cleanup (not current project)"
fi
# Remove all images related to the project
echo "Removing devcontainer images..."
docker images --format "table {{.Repository}}:{{.Tag}}" | grep -E "($purge_pattern)" | awk '{print $1}' | xargs -r docker rmi -f
# Remove all volumes related to the project (with force if needed)
echo "Removing devcontainer volumes..."
docker volume ls --format "{{.Name}}" | grep -E "($purge_pattern)" | while read volume; do
echo "Removing volume: $volume"
docker volume rm -f "$volume" 2>/dev/null || echo "Volume $volume could not be removed (may be in use)"
done
# Ask user if they want to purge CPM cache volume
echo ""
echo "CPM source cache volume (everest-cpm-source-cache) is shared across all workspaces."
read -p "Do you want to purge the CPM cache volume as well? [y/N]: " purge_cache
purge_cache="${purge_cache:-N}"
if [[ "$purge_cache" =~ ^[Yy]$ ]]; then
echo "Removing CPM cache volume..."
if docker volume rm everest-cpm-source-cache 2>/dev/null; then
echo "✔ CPM cache volume removed"
else
echo "⚠ CPM cache volume could not be removed (may be in use or not exist)"
fi
else
echo "Keeping CPM cache volume (will be reused for faster builds)"
fi
# Remove any dangling images and containers
echo ""
echo "Cleaning up dangling resources..."
docker system prune -f
echo ""
echo "✔ Purge complete! All devcontainer resources have been removed."
}
# Function to check if SSH agent is running
check_ssh_agent() {
if [ -z "$SSH_AUTH_SOCK" ] || ! ssh-add -l >/dev/null 2>&1; then
echo "Error: SSH agent is not running or no keys are loaded."
echo "Please start the SSH agent and add your keys:"
echo " eval \$(ssh-agent)"
echo " ssh-add ~/.ssh/id_rsa # or your private key"
echo "Or if you're using a different key:"
echo " ssh-add ~/.ssh/your_private_key"
exit 1
fi
}
# Function to execute a command in the container
exec_devcontainer() {
if is_inside_container; then
echo "✖ You're already inside the container."
echo ""
echo "To run a command, just execute it directly:"
if [ $# -gt 0 ]; then
echo " $@"
else
echo " <your-command>"
fi
exit 1
fi
echo "Checking if development container is running..."
# Check if the devcontainer service is running
if ! docker_compose ps devcontainer | grep -q "Up"; then
echo "Error: Development container is not running."
echo "Please start the container first with: ./devrd start"
echo "Or build and start with: ./devrd build && ./devrd start"
exit 1
fi
echo "Executing command in development container..."
run_in_devcontainer "$@"
}
# Function to get a shell prompt in the container
prompt_devcontainer() {
if is_inside_container; then
echo "✖ You're already inside the container shell."
exit 1
fi
echo "Starting shell in development container..."
exec_devcontainer /bin/bash
}
# Helper function to check if Node-RED is running and get the URL
# Sets nodered_url variable and returns 0 if running, 1 if not
check_nodered_running() {
if is_inside_container; then
nodered_url="http://nodered:1880"
curl -s "$nodered_url/flows" >/dev/null 2>&1 && return 0
else
nodered_url="http://localhost:1880"
docker_compose ps | grep -q "nodered" && return 0
fi
return 1
}
# Helper function to execute a command in the container
# Usage: run_in_devcontainer [--no-tty] <command> [args...]
# Executes directly if inside container, via docker_compose exec if on host
# No error checking - assumes container is running when called from host
# Use --no-tty for non-interactive commands that need output capture
run_in_devcontainer() {
local no_tty=false
if [ "$1" = "--no-tty" ]; then
no_tty=true
shift
fi
if is_inside_container; then
"$@"
else
if [ "$no_tty" = true ]; then
docker_compose exec -T devcontainer "$@"
else
docker_compose exec devcontainer "$@"
fi
fi
}
# Function to list available flows
list_nodered_flows() {
echo ""
echo "Available Node-RED Flows:"
echo "============================="
# Check if Node-RED is running
if ! check_nodered_running; then
echo "✖ Node-RED container is not running"
echo "Please start with './devrd start' first"
return 1
fi
# Find all flow files in the workspace
local flows
if is_inside_container; then
flows=$(find /workspace -name "*-flow.json" -type f 2>/dev/null | sort)
else
flows=$(docker_compose exec -T devcontainer find /workspace -name "*-flow.json" -type f 2>/dev/null | sort)
fi
if [ -z "$flows" ]; then
echo "No flow files found in workspace"
echo ""
echo "Expected pattern: *-flow.json"
echo "Search location: /workspace"
return 1
fi
echo "Found $(echo "$flows" | wc -l) flow file(s):"
echo ""
for flow in $flows; do
# Remove /workspace/ prefix to get relative path from workspace root
local relative_path=$(echo "$flow" | sed 's|^/workspace/||')
echo " Path: $relative_path"
done
echo ""
echo "Usage: ./devrd flow <flow-file-path>"
echo "Example: ./devrd flow EVerest/config/nodered/config-sil-dc-flow.json"
echo ""
}
# Function to switch flow using REST API
switch_nodered_flow() {
local flow_path="$1"
if [ -z "$flow_path" ]; then
echo "Error: Please specify a flow file path"
echo ""
echo "Available flows:"
list_nodered_flows
return 1
fi
# Check if Node-RED is running
if ! check_nodered_running; then
echo "✖ Node-RED container is not running"
echo "Please start with './devrd start' first"
return 1
fi
# Construct full path in container
local full_path="/workspace/$flow_path"
# Check if file exists and is readable, then copy to temp file
if ! run_in_devcontainer --no-tty test -r "$full_path"; then
echo "✖ Flow file not found or not readable: $flow_path"
echo ""
echo "Available flows:"
list_nodered_flows
return 1
fi
# Copy flow to temporary file
run_in_devcontainer --no-tty cat "$full_path" > /tmp/flows.json
echo "Switching Node-RED to flow: $(basename "$flow_path")"
echo "Source: $flow_path"
# Process environment variables in the flow JSON
# Replace "broker": "localhost" with "broker": "mqtt-server"
sed -i 's/"broker": "localhost"/"broker": "mqtt-server"/g' /tmp/flows.json
# Deploy flow via Node-RED REST API
echo "Deploying flow via Node-RED API..."
local response=$(curl -s -w "%{http_code}" -X POST "$nodered_url/flows" \
-H "Content-Type: application/json" \
-d @/tmp/flows.json)
local http_code="${response: -3}"
if [ "$http_code" = "200" ] || [ "$http_code" = "204" ]; then
echo "✔ Node-RED flow deployed successfully via API!"
if is_inside_container; then
echo "Access at: http://nodered:1880/ui (from container) or http://localhost:1880/ui (from host)"
else
echo "Access at: http://localhost:1880/ui"
fi
else
echo "✖ Failed to deploy flow via API (HTTP $http_code)"
echo "Response: ${response%???}"
return 1
fi
# Clean up temporary file
rm -f /tmp/flows.json
}
# Function to display help
show_help() {
echo "Usage: $0 [COMMAND] [OPTIONS]"
echo ""
echo "Commands:"
echo " env Generate .env file with repository information (default)"
echo " build Build the development container"
echo " start [profile] Start containers (profiles: mqtt, ocpp, sil, all)"
echo " stop [profile|pattern] Stop containers by profile (mqtt, ocpp, sil, all) or container name pattern"
echo " purge [pattern] Remove all devcontainer resources (containers, images, volumes)"
echo " Optional pattern to match (default: current folder name)"
echo " exec <command> Execute a command in the development container (requires the container to be running)"
echo " prompt Get a shell prompt in the development container (requires the container to be running)"
echo " flows List available flows"
echo " flow <path> Switch to specific flow file"
echo ""
echo "Options (for env command only):"
echo " -v, --version VERSION Everest tool branch (default: $EVEREST_TOOL_BRANCH, preserves existing if not specified)"
echo " -w, --workspace DIR Workspace directory to map to /workspace in container (default: current directory)"
echo " --help Display this help message"
echo ""
echo "Examples:"
echo " $0 env # Generate .env file with repository information"
echo " $0 build # Build container"
echo " $0 start # Start all containers"
echo " $0 start sil # Start SIL simulation tools (Node-RED, MQTT Explorer)"
echo " $0 start ocpp # Start OCPP services (Steve, OCPP DB, MQTT)"
echo " $0 start mqtt # Start only MQTT server"
echo " $0 stop sil # Stop SIL simulation tools"
echo " $0 stop ev-ws # Stop all containers matching pattern 'ev-ws'"
echo " $0 purge # Remove all devcontainer resources for current folder"
echo " $0 purge my-project # Remove all devcontainer resources matching 'my-project'"
echo " $0 exec ls -la # Execute command in container"
echo " $0 prompt # Get shell prompt in container"
echo " $0 flows # List available flows"
echo " $0 flow <path> # Switch to specific Node-RED flow file"
echo " $0 -w ~/Documents # Map Documents folder to /workspace"
echo " $0 --workspace /opt/tools # Map tools folder to /workspace"
exit 0
}
# Parse command line arguments
COMMAND="env"
ENV_OPTIONS=""
# First pass: collect all options
while [ $# -gt 0 ]; do
case $1 in
-v|--version|-w|--workspace)
# Store env-specific options for later use
ENV_OPTIONS="$ENV_OPTIONS $1 $2"
shift 2
;;
--help)
show_help
;;
exec)
COMMAND="$1"
shift
# For exec, pass all remaining arguments to the exec function
break
;;
env|build|prompt|flows)
COMMAND="$1"
shift
# Don't break here, continue to collect more options
;;
flow)
COMMAND="$1"
shift
# For flow, pass any remaining arguments as flow path
break
;;
purge)
COMMAND="$1"
shift
# For purge, pass any remaining arguments as pattern
break
;;
start|stop)
COMMAND="$1"
shift
# For start/stop, pass any remaining arguments as container name
break
;;
*)
echo "Unknown option: $1"
show_help
;;
esac
done
# Execute the command
case $COMMAND in
env)
# Check SSH agent for Git operations
check_ssh_agent
generate_env
;;
build)
# Only generate env if .env file doesn't exist or is empty
if [ ! -f "$ENV_FILE" ] || [ ! -s "$ENV_FILE" ]; then
# Check SSH agent for Git operations
check_ssh_agent
generate_env
fi
build_container
;;
start)
# Only generate env if .env file doesn't exist or is empty
if [ ! -f "$ENV_FILE" ] || [ ! -s "$ENV_FILE" ]; then
# Check SSH agent for Git operations
check_ssh_agent
generate_env
fi
start_compose_profile "$@"
;;
stop)
stop_compose_profile "$@"
;;
purge)
purge_everything "$@"
;;
exec)
if [ $# -eq 0 ]; then
echo "Error: exec command requires arguments"
show_help
fi
exec_devcontainer "$@"
;;
prompt)
prompt_devcontainer
;;
flows)
list_nodered_flows
;;
flow)
if [ $# -eq 0 ]; then
echo "Error: flow command requires a flow file path"
show_help
fi
switch_nodered_flow "$1"
;;
*)
echo "Unknown command: $COMMAND"
show_help
;;
esac

View File

@@ -0,0 +1,97 @@
#!/bin/bash
# Bash completion for devrd script
# Source this file or add to your .bashrc to enable completion
_devrd_completion() {
local cur prev opts cmds
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
# Available commands
cmds="install env build start stop prompt purge exec flows flow"
# Available options
opts="-v --version -w --workspace --help"
# Function to get available Node-RED flows dynamically
_get_nodered_flows() {
# Get the current project name (same logic as devrd script)
local project_name="${DOCKER_COMPOSE_PROJECT_NAME:-$(basename "$(pwd)")_devcontainer}"
# Check if we're in the right directory and container is running
if [ -f "devrd" ] && docker compose -p "$project_name" -f .devcontainer/docker-compose.yml -f .devcontainer/general-devcontainer/docker-compose.devcontainer.yml ps devcontainer | grep -q "Up"; then
# Get flows from the container and return full paths (relative to workspace)
docker compose -p "$project_name" -f .devcontainer/docker-compose.yml -f .devcontainer/general-devcontainer/docker-compose.devcontainer.yml exec -T devcontainer find /workspace -name "*-flow.json" -type f 2>/dev/null | sed 's|/workspace/||' | sort
else
# Fallback to common flow file paths
echo "EVerest/config/nodered/config-sil-dc-flow.json"
echo "EVerest/config/nodered/config-sil-dc-bpt-flow.json"
echo "EVerest/config/nodered/config-sil-energy-management-flow.json"
echo "EVerest/config/nodered/config-sil-two-evse-flow.json"
echo "EVerest/config/nodered/config-sil-flow.json"
fi
}
# Function to get available container names
_get_container_names() {
echo "mqtt ocpp sil"
}
# If the previous word is an option that takes an argument, complete based on the option
case "$prev" in
-v|--version)
# Complete with common version patterns
COMPREPLY=( $(compgen -W "main master develop release/1.0 release/1.1" -- "$cur") )
return 0
;;
-w|--workspace)
# Complete directories
COMPREPLY=( $(compgen -d -- "$cur") )
return 0
;;
flow)
# Complete with available flow file paths dynamically
local flows
flows=$(_get_nodered_flows)
COMPREPLY=( $(compgen -W "$flows" -- "$cur") )
return 0
;;
start|stop)
# Complete with available container names
local containers
containers=$(_get_container_names)
COMPREPLY=( $(compgen -W "$containers" -- "$cur") )
return 0
;;
exec)
# For exec command, complete with common commands
COMPREPLY=( $(compgen -W "ls pwd cd cmake ninja make" -- "$cur") )
return 0
;;
esac
# If we're completing the first word (command), show commands
if [ $COMP_CWORD -eq 1 ]; then
COMPREPLY=( $(compgen -W "$cmds" -- "$cur") )
return 0
fi
# If we're completing an option, show options
if [[ "$cur" == -* ]]; then
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
return 0
fi
# For other cases, complete with files/directories
COMPREPLY=( $(compgen -f -- "$cur") )
return 0
}
# Register the completion function
complete -F _devrd_completion devrd
complete -F _devrd_completion ./devrd
complete -F _devrd_completion ../devrd
complete -F _devrd_completion ./applications/devrd/devrd

View File

@@ -0,0 +1,90 @@
#!/bin/zsh
# Zsh completion for devrd script
# Source this file or add to your .zshrc to enable completion
_devrd_completion() {
local context state line
typeset -A opt_args
# Available commands
local commands=(
'env:Generate .env file with repository information'
'build:Build the development container'
'start:Start containers (profiles: mqtt, ocpp, sil)'
'stop:Stop containers (profiles: mqtt, ocpp, sil)'
'purge:Remove all devcontainer resources (containers, images, volumes)'
'exec:Execute a command in the container'
'prompt:Get a shell prompt in the container'
'flows:List available flows'
'flow:Switch to specific flow file'
)
# Available options
local options=(
'-v[Everest tool branch]:version:'
'--version[Everest tool branch]:version:'
'-w[Workspace directory]:directory:_files -/'
'--workspace[Workspace directory]:directory:_files -/'
'--help[Display help message]'
)
# Function to get available Node-RED flows dynamically
_get_nodered_flows() {
# Get the current project name (same logic as devrd script)
local project_name="${DOCKER_COMPOSE_PROJECT_NAME:-$(basename "$(pwd)")_devcontainer}"
# Check if we're in the right directory and container is running
if [ -f "devrd" ] && docker compose -p "$project_name" -f .devcontainer/docker-compose.yml -f .devcontainer/general-devcontainer/docker-compose.devcontainer.yml ps devcontainer | grep -q "Up"; then
# Get flows from the container and return full paths (relative to workspace)
docker compose -p "$project_name" -f .devcontainer/docker-compose.yml -f .devcontainer/general-devcontainer/docker-compose.devcontainer.yml exec -T devcontainer find /workspace -name "*-flow.json" -type f 2>/dev/null | sed 's|/workspace/||' | sort
else
# Fallback to common flow file paths
echo "EVerest/config/nodered/config-sil-dc-flow.json"
echo "EVerest/config/nodered/config-sil-dc-bpt-flow.json"
echo "EVerest/config/nodered/config-sil-energy-management-flow.json"
echo "EVerest/config/nodered/config-sil-two-evse-flow.json"
echo "EVerest/config/nodered/config-sil-flow.json"
fi
}
# Function to get available container names
_get_container_names() {
echo "mqtt ocpp sil"
}
# Main completion logic
_arguments -C \
"$options[@]" \
"1: :{_describe 'commands' commands}" \
"*::arg:->args"
case $state in
args)
case $line[1] in
flow)
_values 'flow files' $(_get_nodered_flows)
;;
start|stop)
_values 'profiles' $(_get_container_names)
;;
exec)
_values 'commands' 'ls' 'pwd' 'cd' 'cmake' 'ninja' 'make'
;;
purge)
_files
;;
esac
;;
esac
}
# Register the completion function
if command -v compdef >/dev/null 2>&1; then
compdef _devrd_completion devrd
compdef _devrd_completion ./devrd
compdef _devrd_completion ../devrd
compdef _devrd_completion ./applications/devrd/devrd
else
echo "Warning: zsh completion system not loaded. Add 'autoload -U compinit && compinit' to your .zshrc"
fi