[WiP] Carling Cloud connectivity
This commit is contained in:
196
Python/azure_iot_hub_manage_iot_edge_devices.py
Normal file
196
Python/azure_iot_hub_manage_iot_edge_devices.py
Normal file
@@ -0,0 +1,196 @@
|
||||
from azure.iot.hub import IoTHubRegistryManager
|
||||
from azure.iot.hub.protocol.models import QuerySpecification, Module
|
||||
from azure.iot.hub.models import CloudToDeviceMethod, CloudToDeviceMethodResult
|
||||
from dotenv import load_dotenv
|
||||
from isight_device import iSightDevice
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Header, Footer, ListView, ListItem, Label, Input, Button, Static, DataTable
|
||||
from textual.containers import Vertical, VerticalScroll
|
||||
from textual.binding import Binding
|
||||
from textual.screen import Screen
|
||||
from textual import on
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
load_dotenv()
|
||||
|
||||
class ObjectListItem(ListItem):
|
||||
"""A widget to display an iSightDevice object in the ListView."""
|
||||
|
||||
def __init__(self, device: iSightDevice) -> None:
|
||||
super().__init__()
|
||||
self.device = device
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""Create the displayable content of the list item."""
|
||||
# This line is customized to display the attributes of your iSightDevice object.
|
||||
# It shows site, number, device_id, and version.
|
||||
yield Label(f"{self.device.getDeviceId()} | {self.device.getSite()} | {self.device.getNumber()} ({self.device.getVersion()})")
|
||||
|
||||
class ModuleScreen(Screen):
|
||||
|
||||
BINDINGS = [Binding("escape", "app.pop_screen", "Go Back")]
|
||||
|
||||
def __init__(self, device: iSightDevice, device_modules: list[Module]):
|
||||
super().__init__()
|
||||
self.device = device
|
||||
self.device_modules = device_modules
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header(f"IoT Edge modules of {self.device.getDeviceId}")
|
||||
yield DataTable()
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self) -> None:
|
||||
table = self.query_one(DataTable)
|
||||
table.add_columns("Module", "Status", "Since", "Last activity")
|
||||
for module in self.device_modules:
|
||||
local_connection_time = datetime.fromisoformat(str(module.connection_state_updated_time)).astimezone().replace(microsecond=0).isoformat()
|
||||
local_activity_time = datetime.fromisoformat(str(module.last_activity_time)).astimezone().replace(microsecond=0).isoformat()
|
||||
table.add_row(f"{module.module_id}", f"{module.connection_state}", f"{local_connection_time}", f"{local_activity_time}")
|
||||
|
||||
|
||||
## NEW: The Screen for showing device details and actions
|
||||
class DetailScreen(Screen):
|
||||
"""A screen to display details and actions for a single device."""
|
||||
|
||||
BINDINGS = [
|
||||
Binding("escape", "app.pop_screen", "Go Back"),
|
||||
]
|
||||
|
||||
def __init__(self, device: iSightDevice, registry_manager: IoTHubRegistryManager) -> None:
|
||||
super().__init__()
|
||||
self.device = device
|
||||
self.registry_manager = registry_manager
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header(name=f"Details for {self.device.getDeviceId()}")
|
||||
|
||||
# Use VerticalScroll so the layout works on small terminals
|
||||
with VerticalScroll(id="details-container"):
|
||||
# Static, non-interactive information section
|
||||
yield Static(f"[b]Site:[/b] {self.device.getSite()}", markup=True)
|
||||
yield Static(f"[b]Number:[/b] {self.device.getNumber()}", markup=True)
|
||||
yield Static(f"[b]Version:[/b] {self.device.getVersion()}", markup=True)
|
||||
yield Static(f"[b]Device ID:[/b] {self.device.getDeviceId()}", markup=True)
|
||||
|
||||
# Interactive action buttons
|
||||
yield Static("\n[b]Actions:[/b]", markup=True)
|
||||
yield Button("Check IoT Edge modules", variant="primary", id="modules")
|
||||
|
||||
yield Footer()
|
||||
|
||||
## NEW: Handlers for button presses
|
||||
@on(Button.Pressed, "#modules")
|
||||
def check_modules(self) -> None:
|
||||
device_modules = self.registry_manager.get_modules(self.device.getDeviceId())
|
||||
device_modules.sort(key=lambda m: m.module_id)
|
||||
self.app.push_screen(ModuleScreen(self.device, device_modules))
|
||||
|
||||
# @on(Button.Pressed, "#logs")
|
||||
# def check_logs(self) -> None:
|
||||
# # Placeholder for your logging logic
|
||||
# self.app.notify(f"Fetching logs for {self.device.getDeviceId()}...")
|
||||
|
||||
# @on(Button.Pressed, "#diag")
|
||||
# def run_diagnostics(self) -> None:
|
||||
# # Placeholder for your diagnostics logic
|
||||
# self.app.notify(f"Running diagnostics on {self.device.getDeviceId()}...")
|
||||
|
||||
# @on(Button.Pressed, "#delete")
|
||||
# def delete_device(self) -> None:
|
||||
# # Placeholder for delete logic
|
||||
# self.app.notify(f"Deleting {self.device.getDeviceId()} is not yet implemented.", severity="error")
|
||||
# # You would likely want a confirmation dialog here in a real app.
|
||||
|
||||
|
||||
class DeviceTUI(App):
|
||||
"""An interactive TUI to list and filter iSightDevice objects."""
|
||||
|
||||
BINDINGS = [
|
||||
Binding("q", "quit", "Quit"),
|
||||
]
|
||||
|
||||
def __init__(self, devices: list[iSightDevice], registry_manager: IoTHubRegistryManager):
|
||||
super().__init__()
|
||||
self.all_devices = devices
|
||||
self.filtered_devices = self.all_devices[:]
|
||||
self.registry_manager = registry_manager
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""Create child widgets for the app."""
|
||||
yield Header(name="iSight Device Viewer")
|
||||
yield Input(placeholder="Filter by site...")
|
||||
with Vertical(id="list-container"):
|
||||
yield ListView(*[ObjectListItem(dev) for dev in self.filtered_devices], id="device-list")
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""Called when the app is first mounted."""
|
||||
self.query_one(Input).focus()
|
||||
|
||||
def on_input_changed(self, event: Input.Changed) -> None:
|
||||
"""Handle changes to the input field and filter the list."""
|
||||
filter_text = event.value.lower()
|
||||
self.filter_devices(filter_text)
|
||||
|
||||
def filter_devices(self, filter_text: str):
|
||||
"""Filter the list of devices based on the site."""
|
||||
if filter_text:
|
||||
self.filtered_devices = [
|
||||
dev for dev in self.all_devices if filter_text in dev.getSite().lower()
|
||||
]
|
||||
else:
|
||||
self.filtered_devices = self.all_devices[:]
|
||||
|
||||
list_view = self.query_one(ListView)
|
||||
list_view.clear()
|
||||
for dev in self.filtered_devices:
|
||||
list_view.append(ObjectListItem(dev))
|
||||
|
||||
def on_list_view_selected(self, event: ListView.Selected) -> None:
|
||||
"""Handle item selection in the ListView."""
|
||||
selected_item = event.item
|
||||
if isinstance(selected_item, ObjectListItem):
|
||||
selected_device = selected_item.device
|
||||
# Instead of a notification, we push the new detail screen
|
||||
self.push_screen(DetailScreen(device=selected_device, registry_manager=registry_manager))
|
||||
|
||||
if __name__ == "__main__":
|
||||
CONNECTION_STRING = str(os.getenv("CONNECTION_STRING_INOX_PROD"))
|
||||
if CONNECTION_STRING == "":
|
||||
print("Provide a connection string for the Iot Hub before running the script!")
|
||||
exit(13)
|
||||
|
||||
print(f"Connecting to IoT Hub ", end="")
|
||||
try:
|
||||
registry_manager = IoTHubRegistryManager.from_connection_string(CONNECTION_STRING)
|
||||
except Exception as e:
|
||||
print(f"❌")
|
||||
print(f"Error {e}")
|
||||
exit
|
||||
print(f"✅")
|
||||
|
||||
print(f"Getting list of IoT Edge devices ", end="")
|
||||
try:
|
||||
query_spec = QuerySpecification(query="SELECT * FROM devices WHERE capabilities.iotEdge = true ")
|
||||
query_result = registry_manager.query_iot_hub(query_spec)
|
||||
except Exception as e:
|
||||
print(f"❌")
|
||||
print(f"Error {e}")
|
||||
exit
|
||||
print(f"✅")
|
||||
|
||||
print(f"Sorting {len(query_result.items)} devices ", end="")
|
||||
devices = []
|
||||
for item in query_result.items:
|
||||
devices.append(iSightDevice(str(item.device_id), str(item.tags['site']), int(item.tags['number']), str(item.tags['version'])))
|
||||
devices.sort(key = lambda d: (d.site, d.number))
|
||||
print(f"✅")
|
||||
|
||||
app = DeviceTUI(devices=devices, registry_manager=registry_manager)
|
||||
app.run()
|
||||
Reference in New Issue
Block a user