Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions .github/workflows/build-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,17 @@ jobs:
release_type: ${{ steps.version.outputs.release_type }}
docker_tag: ${{ steps.docker_tags.outputs.tag }}
steps:
- name: Clean workspace
if: always()
run: |
sudo chown -R $USER:$USER ${{ github.workspace }} || true
sudo rm -rf ${{ github.workspace }}/* ${{ github.workspace }}/.* 2>/dev/null || true

- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
clean: false

- name: Determine Docker tags and cache
id: docker_tags
Expand Down Expand Up @@ -145,10 +152,17 @@ jobs:
platform: linux/arm64
platform_tag: arm64
steps:
- name: Clean workspace
if: always()
run: |
sudo chown -R $USER:$USER ${{ github.workspace }} || true
sudo rm -rf ${{ github.workspace }}/* ${{ github.workspace }}/.* 2>/dev/null || true

- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
clean: false

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
Expand Down Expand Up @@ -192,7 +206,9 @@ jobs:
if: always()
run: |
rm -rf ${{ env.CACHE_PATH }}
mv ${{ env.CACHE_PATH_NEW }} ${{ env.CACHE_PATH }}
if [ -d "${{ env.CACHE_PATH_NEW }}" ]; then
mv ${{ env.CACHE_PATH_NEW }} ${{ env.CACHE_PATH }}
fi

merge:
name: Merge Multi-Arch Image
Expand Down Expand Up @@ -229,6 +245,8 @@ jobs:
- name: Checkout repository
if: github.ref == 'refs/heads/main'
uses: actions/checkout@v4
with:
clean: false

- name: Docker Hub Description
if: github.ref == 'refs/heads/main'
Expand All @@ -252,6 +270,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
clean: false

- name: Generate commit history
id: changelog
Expand Down Expand Up @@ -350,6 +369,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
clean: false

- name: Collect commit information
id: collect
Expand Down Expand Up @@ -585,4 +605,4 @@ jobs:
body: newBody
});

console.log(`✅ Updated PR #${context.issue.number}`);
console.log(`✅ Updated PR #${context.issue.number}`);
13 changes: 10 additions & 3 deletions .github/workflows/snd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,17 @@ jobs:
platform: linux/arm64
platform_tag: arm64
steps:
- name: Clean workspace
if: always()
run: |
sudo chown -R $USER:$USER ${{ github.workspace }} || true
sudo rm -rf ${{ github.workspace }}/* ${{ github.workspace }}/.* 2>/dev/null || true

- name: Checkout repository
uses: actions/checkout@v4
with:
ref: snd
clean: false

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
Expand Down Expand Up @@ -70,7 +77,9 @@ jobs:
if: always()
run: |
rm -rf ${{ env.CACHE_PATH }}
mv ${{ env.CACHE_PATH_NEW }} ${{ env.CACHE_PATH }}
if [ -d "${{ env.CACHE_PATH_NEW }}" ]; then
mv ${{ env.CACHE_PATH_NEW }} ${{ env.CACHE_PATH }}
fi

merge:
name: Merge Multi-Arch Image
Expand Down Expand Up @@ -102,5 +111,3 @@ jobs:
docker pull ${{ vars.DOCKERHUB_USERNAME }}/simpleclouddetect:snd
IMAGE_SIZE=$(docker images --format "{{.Size}}" ${{ vars.DOCKERHUB_USERNAME }}/simpleclouddetect:snd | head -n1)
echo "Docker image size: $IMAGE_SIZE"


33 changes: 26 additions & 7 deletions alpaca/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ def __init__(self, alpaca_config: AlpacaConfig, detect_config: DetectConfig):
self.transaction_lock = threading.Lock()

# Device state
self.connected_clients: Dict[Tuple[str, int], datetime] = {} # (IP, ClientID) -> ConnectionTime
self.connected_clients: Dict[Tuple[str, int], datetime] = {} # (IP, ClientID) -> Connection Start Time
self.client_last_seen: Dict[Tuple[str, int], datetime] = {} # (IP, ClientID) -> Last Heartbeat Time
self.disconnected_clients: Dict[Tuple[str, int], Tuple[datetime, datetime]] = {} # (IP, ClientID) -> (ConnectionTime, DisconnectionTime)
self.connection_lock = threading.Lock()
self.connecting = False
Expand Down Expand Up @@ -167,24 +168,36 @@ def _prune_stale_clients(self):
cutoff_time = now - timedelta(seconds=CLIENT_TIMEOUT_SECONDS)

stale_clients = []
for key, last_seen in list(self.connected_clients.items()):
# Check last_seen for staleness, not initial connection time
for key, last_seen in list(self.client_last_seen.items()):
if last_seen < cutoff_time:
stale_clients.append(key)

for key in stale_clients:
client_ip, client_id = key
conn_time = self.connected_clients[key]

# Retrieve original connection time for the record
conn_time = self.connected_clients.get(key, now)

# Move to disconnected list
self.disconnected_clients[key] = (conn_time, now)
del self.connected_clients[key]

# Remove from active tracking
if key in self.connected_clients:
del self.connected_clients[key]
if key in self.client_last_seen:
del self.client_last_seen[key]

logger.warning(f"Watchdog: Pruned stale client {client_ip} (ID: {client_id}) - "
f"inactive for {(now - conn_time).total_seconds():.0f}s")
f"inactive for {(now - last_seen).total_seconds():.0f}s")

def register_heartbeat(self, client_ip: str, client_id: int):
"""Update the last seen timestamp for a connected client"""
with self.connection_lock:
key = (client_ip, client_id)
if key in self.connected_clients:
self.connected_clients[key] = get_current_time(self.alpaca_config.timezone)
# Only update last_seen, preserve connected_clients (start time)
self.client_last_seen[key] = get_current_time(self.alpaca_config.timezone)

def _setup_mqtt(self):
"""Setup and return MQTT client based on detect_config"""
Expand Down Expand Up @@ -334,7 +347,9 @@ def connect(self, client_ip: str, client_id: int):
"""Connect a client to the device"""
with self.connection_lock:
key = (client_ip, client_id)
self.connected_clients[key] = get_current_time(self.alpaca_config.timezone)
current_time = get_current_time(self.alpaca_config.timezone)
self.connected_clients[key] = current_time
self.client_last_seen[key] = current_time

# Remove from disconnected clients if reconnecting
if key in self.disconnected_clients:
Expand All @@ -358,6 +373,7 @@ def disconnect(self, client_ip: str = None, client_id: int = None):
disc_time = get_current_time(self.alpaca_config.timezone)
self.disconnected_clients[key] = (conn_time, disc_time)
self.connected_clients.clear()
self.client_last_seen.clear()
self.disconnected_at = disc_time
if self.connected_at:
duration = (self.disconnected_at - self.connected_at).total_seconds()
Expand All @@ -372,6 +388,9 @@ def disconnect(self, client_ip: str = None, client_id: int = None):
self.disconnected_clients[key] = (conn_time, disc_time)

del self.connected_clients[key]
if key in self.client_last_seen:
del self.client_last_seen[key]

logger.info(f"Client disconnected: {client_ip} (ID: {client_id}). Total clients: {len(self.connected_clients)}")

if len(self.connected_clients) == 0:
Expand Down
File renamed without changes.
Empty file removed verify_config.py
Empty file.
Loading