Comprendere lastruttura dell'API¶
Prima di scrivere il codice, esaminiamo i principali endpoint che utilizzeremo:
1. Ottieni tuttii lavori¶
GET /api/v6.8/lavori
Restituisce un elenco di tutti i lavori in esecuzione sul computer con informazioni di base quali ora di inizio, ora di fine e stato.
2. Ottienidettagli specifici sul lavoro¶
GET /api/v6.8/lavori/{idlavoro}
Restituisce informazioni dettagliate su un lavoro specifico, tra cui:
ora di inizio: Quando il lavoro è iniziatotempo terminato: Quando il processo è terminato (null se ancora in esecuzione)risultato: Stato del lavoro (In attesa,Di successo,Annullato dall'utente,Fallito)durate: Ripartizione del tempo di esposizione, tempo di ricopertura, ecc.materiale,compito,livelloInAttivitàCorrente: Parametri di costruzione
3. Ottenere i messaggi utente per unlavoro¶
GET /api/v6.8/software/messaggiutente
Restituisce dettagli sui messaggi dell'utente, inclusi
Viene restituito un elenco di messaggi o nulla se non esistono messaggi.
gravità: Gravità del messaggio utenteID lavoro: l'identificatore univoco del processo di compilazione,tempoAumentato: il timestamp in cui sono stati creati i messaggi,messaggio: il messaggio stesso,
L'API utilizza l'autenticazione OAuth2 e restituisce i dati in formato JSON. Tutti i timestamp sono in UTC.
Per una panoramica completa dell'interfaccia API Web, consultare l'appendice Documentazione API.
Configurazione dell'ambiente¶
Per prima cosa, installiamo i pacchetti Python necessari e configuriamo le nostre importazioni.
importare richieste
importare tempo
importazione json
da datetime import datetime, timezone
da digitazione import Dict, Optional, Tuple
import smtplib
da email.mime.text import MIMEText
da email.mime.multipart import MIMEMultipart
da dotenv import load_dotenv
import os
da datetime import timedelta
# A scopo dimostrativo, disabiliteremo gli avvisi SSL
# In produzione, utilizzare certificati SSL adeguati!
import urllib3
urllib3.disable_warnings(urllib3.eccezioni.Avviso di richiesta non sicura)
stampa("✓ Tutti i pacchetti sono stati caricati correttamente!")
Configurazione¶
Configuriamo i parametri di connessione. In un ambiente di produzione reale, per le credenziali si utilizzerebbero variabili di ambiente o un file di configurazione sicuro.
Per accedere all'interfaccia della macchina, è necessario creare delle credenziali API. Ciò può essere fatto comodamente tramite EOSCONNECT Core . Apri la pagina web della tua stampante in un browser: nel nostro caso, sarebbe https://si16120019/.
In molti casi, il browser visualizzerà un avviso che indica che il sito non è sicuro: non preoccuparti, questo avviso appare perché le macchine EOS sono dotate di certificati autofirmati per impostazione predefinita (vedi anche Comprensione dei certificati autofirmati nell'appendice).
Nel menu Impostazioni autorizzazione, è possibile generare una nuova coppia ID cliente e segreto utilizzando il pulsante (+).
Le credenziali API generate di recente sono memorizzate nel .env file.
Perché utilizzare un .env file?
Memorizzare credenziali sensibili come chiavi API e password direttamente nel codice rappresenta un rischio per la sicurezza, specialmente se si condividono i propri notebook o li si inserisce in sistemi di controllo delle versioni come Git. A .env Il file fornisce una soluzione pulita:
- Sicurezza: le credenziali sono conservate separatamente dal codice
- Praticità: facile aggiornare le credenziali senza modificare il codice
- Flessibilità: Ambienti diversi (sviluppo, produzione) possono utilizzare diversi
.envfile - Best practice: segue la metodologia "12-factor app" per la gestione della configurazione
Impostazione dellevariabili¶
Di seguito, determiniamo la versione dell'API EOSCONNECT Core che desideriamo utilizzare. EOSCONNECT Core retrocompatibilità per l'API Web, il che significa che è possibile interrogare tramite l'API Web sia le macchine con software attuale che quelle con software precedente. Leggiamo la versione più recente direttamente dalla macchina: al momento della pubblicazione di questo post, l'ultima versione disponibile era la versione v6.8
# Load environment variables from .env file
load_dotenv()
# Load API credentials from environment variables
HOSTNAME = os.getenv("HOSTNAME")
API_VERSION_INFO_URL = f"https://{HOSTNAME}/api/supportedVersions"
# Fetch available API versions
# Check if API_VERSION is set in environment variables
API_VERSION = os.getenv("API_VERSION")
if API_VERSION:
# Use the version from environment variable
print(f"✓ Using API version from environment variable: {API_VERSION}")
else:
# Fetch API versions from the endpoint
print(f"\n🔍 Fetching available API versions from {API_VERSION_INFO_URL}...")
response = requests.get(API_VERSION_INFO_URL, verify=False, timeout=10)
versions_data = response.json()
# Extract version strings from the response
available_versions = [f"v{v['majorVersion']}.{v['minorVersion']}" for v in versions_data]
if available_versions:
# Sort versions to get the latest
API_VERSION = sorted(available_versions, key=lambda v: [int(x) for x in v.lstrip('v').split('.')])[-1]
print(f"✓ Available API versions: {', '.join(available_versions)}")
print(f"✓ Using latest version: {API_VERSION}")
else:
# Fallback to default version
API_VERSION = "v6.0"
print(f"⚠️ No versions found in response, using default: {API_VERSION}")
Caricamentodelle credenziali API¶
Ora leggiamo le credenziali API dal file .env memorizzare in memoria
from dotenv import load_dotenv
import os
from datetime import timedelta
# Load environment variables from .env file
load_dotenv()
API_BASE_URL = f"https://{HOSTNAME}/api/{API_VERSION}"
API_CLIENT_ID = os.getenv("API_CLIENT_ID")
API_CLIENT_SECRET = os.getenv("API_CLIENT_SECRET")
print("✓ Environment variables loaded")
print(f" - Client ID: {API_CLIENT_ID}")
print(f" - Client Secret: {'*' * len(API_CLIENT_SECRET) if API_CLIENT_SECRET else 'Not set'}")
print(f" - Base URL: {API_BASE_URL}")
Recupero deltoken di accesso¶
Per comunicare con l'API Web EOSCONNECT, abbiamo bisogno di un token di accesso. Questo token funge da chiave digitale che:
- Autenticazione: conferma che siamo autorizzati ad accedere all'API
- Autorizzazione: definisce quali azioni siamo autorizzati a eseguire (in base alle autorizzazioni del cliente)
- Sicurezza: protegge la macchina da accessi non autorizzati
Il token viene richiesto tramite OAuth2 Client Credentials Flow, una procedura standardizzata per la comunicazione tra macchine. Le credenziali API (ID cliente e segreto) che abbiamo creato in precedenza EOSCONNECT Core vengono scambiate con un token di accesso a tempo limitato.
Importante: il token ha un periodo di validità limitato (in genere 1 ora). Per le applicazioni con durata maggiore, il token deve essere rinnovato prima della scadenza.
def get_oauth_token(client_id: str, client_secret: str) -> Optional[Dict]:
"""
Fetch OAuth2 token using client credentials flow.
Args:
client_id: OAuth2 client ID
client_secret: OAuth2 client secret
Returns:
Dictionary containing token information or None if request fails
"""
token_url = f"https://{HOSTNAME}/auth/connect/token"
# Prepare the request data for client credentials grant
data = {
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret
}
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
try:
response = requests.post(token_url, data=data, headers=headers, verify=False, timeout=10)
response.raise_for_status()
token_data = response.json()
return token_data
except requests.exceptions.RequestException as e:
print(f"❌ Error fetching OAuth token: {e}")
return None
def refresh_token() -> bool:
"""
Refresh the OAuth2 token and update global HEADERS.
Returns:
True if token refresh was successful, False otherwise
"""
global API_TOKEN, HEADERS
token_data = get_oauth_token(API_CLIENT_ID, API_CLIENT_SECRET)
if token_data:
API_TOKEN = token_data.get("access_token", "")
token_type = token_data.get("token_type", "Bearer")
HEADERS = {
"Authorization": f"{token_type} {API_TOKEN}",
"Accept": "application/json"
}
return True
else:
print("❌ Failed to refresh token")
return False
# Fetch the token
token_response = get_oauth_token(API_CLIENT_ID, API_CLIENT_SECRET)
if token_response:
# Extract token details
API_TOKEN = token_response.get("access_token", "")
token_type = token_response.get("token_type", "Bearer")
expires_in = token_response.get("expires_in", 0)
# Update the headers with the new token
HEADERS = {
"Authorization": f"{token_type} {API_TOKEN}",
"Accept": "application/json"
}
# Calculate expiry time
current_time = datetime.now(timezone.utc)
expiry_time = current_time + timedelta(seconds=expires_in)
# Display token information
print("📋 Token Details:")
print(f" Token Type: {token_type}")
print(f" Expires In: {expires_in} seconds ({expires_in / 3600:.2f} hours)")
print(f" Current Time (UTC): {current_time.strftime('%Y-%m-%d %H:%M:%S')}")
print(f" Expiry Time (UTC): {expiry_time.strftime('%Y-%m-%d %H:%M:%S')}")
print(f" Token (first 50 chars): {API_TOKEN[:50]}...")
print(f"\n✓ Authorization headers updated")
else:
print("⚠️ Failed to retrieve token. Please check your credentials.")
Passaggio 1: connessioneall'API¶
Creiamo delle funzioni per recuperare i dettagli dei lavori e i messaggi degli utenti dall'API. Questo dimostra quanto sia semplice interagire con l'interfaccia EOSCONNECT!
def get_last_job() -> Optional[Dict]:
"""
Fetch the most recent job from the printer.
Returns:
Dictionary containing the last job's details or None if request fails
"""
url = f"{API_BASE_URL}/jobs/last"
try:
response = requests.get(url, headers=HEADERS, verify=False, timeout=10)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"❌ Error fetching last job: {e}")
return None
def get_user_message(jobId : str, limit: int = 20) -> Optional[Dict]:
"""
Fetch the current user message displayed on the printer.
Returns:
Dictionary containing the user message or None if request fails
"""
url = f"{API_BASE_URL}/software/usermessages"
params = {"jobId": jobId, "limit":limit}
try:
response = requests.get(url, headers=HEADERS, params=params, verify=False, timeout=10)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"❌ Error fetching user message: {e}")
return None
def get_all_jobs(limit: int = 10, from_date: Optional[str] = None, to_date: Optional[str] = None) -> Optional[list]:
"""
Fetch a list of recent jobs.
Args:
limit: Maximum number of jobs to retrieve
from_date: Filter jobs starting from this date (ISO 8601 format, e.g., '2024-01-01T00:00:00Z')
to_date: Filter jobs up to this date (ISO 8601 format, e.g., '2024-12-31T23:59:59Z')
Returns:
List of job summaries or None if request fails
"""
url = f"{API_BASE_URL}/jobs"
params = {"take": limit}
# Add optional date filters if provided
if from_date:
params["from"] = from_date
if to_date:
params["to"] = to_date
try:
response = requests.get(url, headers=HEADERS, params=params, verify=False, timeout=10)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"❌ Error fetching jobs list: {e}")
return None
print("✓ API functions defined")
Passaggio 2: Verifica dellaconnessione¶
Verifichiamo la nostra connessione recuperando i lavori recenti dalla stampante. Questo ci aiuterà a comprendere la struttura dei dati.
Il fetch_jobs La funzione interroga la macchina per tutti i lavori avviati nell'intervallo di tempo compreso tra da_data e ad oggi. Utilizziamo la funzione API definita in precedenza ottieni_tutti_i_lavori a questo scopo. Il risultato della funzione API viene formattato e quindi visualizzato in un'interfaccia utente Gradio. Il codice per la rappresentazione grafica non ha nulla a che vedere con EOSCONNECT Core è stato spostato nel ui.py file per chiarezza.
Diamo un'occhiata ai dettagli del lavoro:
- Il
idè un identificatore univoco per il lavoro. Purtroppo, non fornisce alcuna informazione sul nostro compito (ricetta), poiché rappresenta semplicemente una combinazione del numero di serie della macchina e di un timestamp. - Il
risultatoè una proprietà che ci interessa. Vogliamo essere avvisati quando un lavoro è stato completato e se ha avuto esito positivo o meno.
import pandas as pd
from datetime import datetime, timedelta
from ui import create_job_browser_ui
def fetch_jobs(from_date, to_date):
"""
Fetch jobs within the specified date range and display them in a table.
Args:
from_date: Start date for filtering jobs
to_date: End date for filtering jobs
Returns:
Pandas DataFrame with job information
"""
# Convert dates to ISO 8601 format with time
from_datetime = f"{from_date}T00:00:00.000Z" if from_date else None
to_datetime = f"{to_date}T23:59:59.999Z" if to_date else None
# Fetch jobs from API
refresh_token()
jobs = get_all_jobs(limit=500, from_date=from_datetime, to_date=to_datetime)
if not jobs:
return pd.DataFrame(columns=['id', 'result', 'timeStarted', 'timeEnded', 'task', 'material'])
# Extract relevant fields
job_data = []
for job in jobs:
job_data.append({
'id': job.get('id', 'N/A'),
'result': job.get('result', 'N/A'),
'timeStarted': job.get('timeStarted', 'N/A'),
'timeEnded': job.get('timeEnded', 'N/A'),
'task': job.get('task', 'N/A'),
'material': job.get('material', 'N/A')
})
# Create DataFrame
df = pd.DataFrame(job_data)
return df
# Launch the interface
demo = create_job_browser_ui(fetch_jobs)
demo.launch(share=False, inline=True)
Esempio: questo è un esempio di output
Ora vediamo una panoramica dei lavori creati più di recente. A questo punto, a seconda del risultato del lavoro, possiamo decidere se recuperare ulteriori dettagli o meno. Nel nostro caso, siamo interessati solo a dettagli aggiuntivi, in particolare ai messaggi degli utenti, se un lavoro è stato annullato o terminato a causa di un errore.
Passaggio 3:Logica di monitoraggio dei lavori¶
Ora implementiamo la logica di monitoraggio principale. Questa funzione verifica se l'ultimo lavoro eseguito è stato completato con successo o meno. Se il lavoro è stato interrotto o terminato a causa di un errore, recuperiamo i messaggi dell'utente per raccogliere tutte le informazioni rilevanti relative alla cancellazione.
def check_last_job_status(last_job_id: Optional[str] = None) -> Optional[Dict]:
"""
Check the status of the last job.
Args:
last_job_id: Optional job ID to compare against. If provided and matches
the current last job, returns None (no change detected)
Returns:
Dictionary containing:
- 'result': Job result status (Pending, Successful, Failed, UserCanceled)
- 'jobId': The job ID
- 'task': The task name
- 'timeStarted': When the job started
- 'timeEnded': When the job ended (None if still running)
- 'userMessages': List of user messages (only for failed jobs)
Returns None if last_job_id matches the current last job ID
"""
refresh_token()
# Fetch the last job
last_job = get_last_job()
if not last_job:
return {
'result': 'Error',
'jobId': None,
'task': None,
'timeStarted': None,
'timeEnded': None,
'userMessages': None,
'error': 'Failed to fetch last job'
}
job_id = last_job.get('id', 'Unknown')
# If last_job_id is provided and matches current job, return None
if last_job_id is not None and job_id == last_job_id:
return None
result = last_job.get('result', 'Unknown')
task = last_job.get('task', 'N/A')
time_started = last_job.get('timeStarted', 'N/A')
time_ended = last_job.get('timeEnded', 'N/A')
# Prepare base response
response = {
'result': result,
'jobId': job_id,
'task': task,
'timeStarted': time_started,
'timeEnded': time_ended,
'userMessages': None
}
# If job failed or was canceled, fetch user messages
if result in ['Failed', 'UserCanceled']:
user_messages = get_user_message(job_id, limit=20)
response['userMessages'] = user_messages if user_messages else []
return response
print("✓ Last job status check function defined")
import pandas as pd
from ui import create_job_status_ui
# Track the last job ID
last_job_id_tracker = None
def fetch_job_status():
"""
Fetch the last job status and format it for display.
Returns a tuple of (job_info_df, user_messages_df)
"""
global last_job_id_tracker
result = check_last_job_status(last_job_id_tracker)
if result is None:
# No new job detected
return (
pd.DataFrame([{"Info": f"No new job detected or job status unchanged. Tracked ID: {last_job_id_tracker}"}]),
pd.DataFrame()
)
# Update tracker
last_job_id_tracker = result.get('jobId')
# Create job info DataFrame
job_info = {
'Field': ['Job ID', 'Task', 'Result', 'Time Started', 'Time Ended'],
'Value': [
result.get('jobId', 'N/A'),
result.get('task', 'N/A'),
result.get('result', 'N/A'),
result.get('timeStarted', 'N/A'),
result.get('timeEnded', 'N/A')
]
}
job_info_df = pd.DataFrame(job_info)
# Create user messages DataFrame
user_messages = result.get('userMessages')
if user_messages:
messages_data = []
for msg in user_messages:
messages_data.append({
'Severity': msg.get('severity', 'N/A'),
'timeRaised': msg.get('timeRaised', 'N/A'),
'message': msg.get('message', 'N/A'),
'additionalInfo': msg.get('additionalInfo', 'N/A')
})
user_messages_df = pd.DataFrame(messages_data)
else:
user_messages_df = pd.DataFrame([{"Info": "No user messages available"}])
return job_info_df, user_messages_df
def clear_last_job_id_tracker():
global last_job_id_tracker
last_job_id_tracker = None
return [],[]
# Create Gradio interface for job status
job_status_demo = create_job_status_ui(fetch_job_status, clear_last_job_id_tracker)
# Launch the interface
job_status_demo.launch(share=False, inline=True)
Esempio: questo è un esempio di output
In questo esempio, possiamo vedere che l'ultimo lavoro creato non è stato completato con successo. Dai messaggi dell'utente risulta che l'operatore della macchina ha interrotto il lavoro.
Appendice:¶
Visualizzazione della documentazione EOSCONNECT Core con OpenAPIEditor¶
È possibile utilizzare il seguente editor Swagger ufficiale: https://editor.swagger.io/ per rendere il swagger.json file. Questo ti offre una panoramica di tutte le chiamate API disponibili fornite da EOSCONNECT Core. Un'interfaccia simile è offerta anche direttamente da EOSCONNECT Core e si trova nella sezione Web API / Data Points.
- Accedi all'editor ufficiale Swagger: https://editor.swagger.io/
- Clicca su File → Importa URL
- Seleziona il
swagger.jsonfile
Comprenderei certificati autofirmati¶
Quando ti connetti a un sito web, il tuo browser verifica se il certificato di sicurezza del sito è firmato da un'autorità affidabile (come un'organizzazione ben nota).
I certificati autofirmati sono creati dal proprietario del sito web stesso, non da un'autorità affidabile. È come scrivere la propria carta d'identità invece di ottenerne una dal governo.
Il browser visualizza un avviso perché non è in grado di verificare l'affidabilità del certificato, che potrebbe essere legittimo (come nel caso delle stampanti EOS) o potenzialmente pericoloso. Nel caso delle macchine EOS, la connessione è sicura perché si collega alla propria stampante locale e non a un sito web casuale su Internet.
Pensateci in questi termini: la stampante sta dicendo "Fidati di me, sono chi dico di essere" senza che una terza parte ne garantisca l'autenticità.