Comprendre lastructure de l'API¶

Avant de coder, explorons les principaux points de terminaison que nous utiliserons :

1. Obtenir tousles emplois¶

GET /api/v6.8/jobs

Renvoie une liste de toutes les tâches en cours sur la machine avec des informations de base telles que l'heure de début, l'heure de fin et le statut.

2. Obtenirdes détails spécifiques sur le poste¶

GET /api/v6.8/jobs/{jobId}

Renvoie des informations détaillées sur une tâche spécifique, notamment :

  • heure de début: Quand le travail a commencé
  • fin du temps: Lorsque la tâche est terminée (null si elle est toujours en cours d'exécution)
  • résultat: Statut de l'emploi (En attente, Réussi, UtilisateurAnnulé, Échec)
  • durées: Répartition du temps d'exposition, du temps de recouvrement, etc.
  • matériau, tâche, coucheDansTâcheActuelle: Paramètres de construction

3. Obtenir les messages utilisateur pour unetâche¶

GET /api/v6.8/software/usermessages

Renvoie des détails sur les messages de l'utilisateur, notamment

Une liste de messages est renvoyée ou rien si aucun message n'existe.

  • gravité: Gravité du message utilisateur
  • jobId: identifiant unique de la tâche de compilation,
  • temps augmenté: l'horodatage auquel les messages ont été créés,
  • message: le message lui-même,

L'API utilise l'authentification OAuth2 et renvoie les données au format JSON. Tous les horodatages sont en UTC.

Pour obtenir un aperçu complet de l'interface API Web, veuillez vous reporter à l'annexe Documentation API.

Configuration de l'environnement¶

Tout d'abord, installons les paquets Python requis et configurons nos importations.

Dans [ ] :
demandes d'importation demandes
importation temps
import json
from datetime import datetime, timezone
from typing import Dict, Optional, Tuple
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from dotenv import load_dotenv
import os
from datetime import timedelta

# À des fins de démonstration, nous désactiverons les avertissements SSL
# En production, utilisez des certificats SSL appropriés !
import urllib3
urllib3.disable_warnings(urllib3.exceptions.Avertissement de requête non sécurisée)

imprimer(« ✓ Tous les paquets ont été chargés avec succès ! »)

Configuration¶

Configurons les paramètres de connexion. Dans un environnement de production réel, vous utiliseriez des variables d'environnement ou un fichier de configuration sécurisé pour les informations d'identification.

Pour accéder à l'interface de la machine, nous devons créer des identifiants API. Cela peut être fait facilement via EOSCONNECT Core . Ouvrez la page Web de votre imprimante dans un navigateur. Dans notre cas, il s'agit de https://si16120019/.

image.png

Dans de nombreux cas, le navigateur affichera un avertissement indiquant que le site n'est pas sécurisé. Ne vous inquiétez pas, cet avertissement apparaît car les machines EOS sont équipées par défaut de certificats auto-signés (voir également la section Comprendre les certificats auto-signés dans l'annexe).

image-2.png

Dans le menu Paramètres d'autorisation, nous pouvons générer un nouvel identifiant client et une nouvelle clé secrète à l'aide du bouton (+).

image-3.png

Les informations d'identification API récemment générées sont stockées dans le .env fichier.

Pourquoi utiliser un .env fichier ?

Le stockage d'informations d'identification sensibles telles que les clés API et les mots de passe directement dans votre code représente un risque pour la sécurité, en particulier si vous partagez vos blocs-notes ou les enregistrez dans des systèmes de contrôle de version tels que Git. A .env Le fichier fournit une solution claire :

  • Sécurité: les identifiants sont conservés séparément de votre code.
  • Commodité: mise à jour facile des informations d'identification sans modification de votre code
  • Flexibilité: Différents environnements (développement, production) peuvent utiliser différents .env fichiers
  • Meilleure pratique: suit la méthodologie « 12-factor app » pour la gestion de la configuration

Définition desvariables¶

Dans la suite, nous déterminons la version de l'API EOSCONNECT Core que nous souhaitons utiliser. EOSCONNECT Core une compatibilité ascendante pour l'API Web, ce qui signifie que les machines équipées du logiciel actuel et des versions antérieures peuvent être interrogées via l'API Web. Nous lisons la dernière version directement à partir de la machine. Au moment de la publication de cet article, la dernière version disponible était la version v6.8

Dans [ ] :
# 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}")

Chargementdes informations d'identification API¶

Nous lisons maintenant les informations d'identification API à partir du fichier .env enregistrer dans la mémoire

Dans [ ] :
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}")

Récupération dujeton d'accès¶

Pour communiquer avec l'API Web EOSCONNECT, nous avons besoin d'un jeton d'accès. Ce jeton sert de clé numérique qui :

  • Authentification: confirme que nous sommes autorisés à accéder à l'API.
  • Autorisation: définit les actions que nous sommes autorisés à effectuer (en fonction des autorisations accordées par le client).
  • Sécurité: protège la machine contre tout accès non autorisé.

Le jeton est demandé via le flux d'informations d'identification du client OAuth2, une procédure standardisée pour la communication entre machines. Les informations d'identification API (ID client et secret) que nous avons précédemment créées dans EOSCONNECT Core sont échangées contre un jeton d'accès à durée limitée.

Important: le jeton a une durée de validité limitée (généralement 1 heure). Pour les applications fonctionnant plus longtemps, le jeton doit être renouvelé avant son expiration.

Dans [ ] :
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.")

Étape 1 : Connexion àl'API¶

Créons des fonctions pour récupérer les détails des tâches et les messages des utilisateurs à partir de l'API. Cela montre à quel point il est simple d'interagir avec l'interface EOSCONNECT !

Dans [ ] :
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")

Étape 2 : Test de laconnexion¶

Testons notre connexion en récupérant les travaux récents de l'imprimante. Cela nous aidera à comprendre la structure des données.

Le récupérer_les_tâches La fonction interroge la machine pour tous les travaux qui ont été lancés dans la plage horaire comprise entre date_de_début et à ce jour. Nous utilisons la fonction API précédemment définie. obtenir_tous_les_emplois à cette fin. Le résultat de la fonction API est formaté puis affiché dans une interface utilisateur Gradio. Le code pour la représentation graphique n'a rien à voir avec EOSCONNECT Core a été déplacé vers le ui.py fichier pour plus de clarté.

Examinons les détails du poste :

  • Le id est un identifiant unique pour la tâche. Malheureusement, il ne fournit aucune information sur notre tâche (recette), car il représente simplement une combinaison du numéro de série de la machine et d'un horodatage.
  • Le résultat est une propriété qui nous intéresse. Nous voulons être informés lorsqu'une tâche est terminée et savoir si elle a été effectuée avec succès ou non.
Dans [ ] :
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)

Exemple : voici un exemple de résultat image.png

Nous voyons maintenant un aperçu des tâches les plus récemment créées. À présent, en fonction du résultat de la tâche, nous pouvons décider si nous souhaitons obtenir plus de détails ou non. Dans notre cas, nous ne sommes intéressés par les détails supplémentaires (en particulier les messages utilisateur) que si une tâche a été annulée ou terminée en raison d'une erreur.

Étape 3 :Logique de surveillance des tâches¶

Passons maintenant à la mise en œuvre de la logique de surveillance principale. Cette fonction vérifie si la dernière tâche exécutée s'est terminée avec succès ou non. Si la tâche a été interrompue ou arrêtée en raison d'une erreur, nous récupérons alors les messages utilisateur afin de rassembler toutes les informations pertinentes concernant l'annulation.

Dans [ ] :
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")
Dans [ ] :
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)

Exemple : voici un exemple de résultat image.png

Dans cet exemple, nous pouvons voir que la tâche la plus récemment créée n'a pas été exécutée avec succès. En consultant les messages utilisateur, nous constatons que l'opérateur de la machine a interrompu la tâche.

Annexe :¶

Affichage de la documentation EOSCONNECT Core avecl'éditeur OpenAPI¶

Vous pouvez utiliser l'éditeur Swagger officiel suivant : https://editor.swagger.io/ pour rendre le swagger.json . Cela vous donne un aperçu de tous les appels API disponibles fournis par EOSCONNECT Core. Une interface similaire est également proposée directement par EOSCONNECT Core et se trouve dans la section API Web / Points de données.

  1. Accédez à l'éditeur Swagger officiel : https://editor.swagger.io/
  2. Cliquez sur Fichier → Importer une URL
  3. Sélectionnez le swagger.json fichier

Comprendreles certificats auto-signés¶

Lorsque vous vous connectez à un site Web, votre navigateur vérifie si le certificat de sécurité du site est signé par une autorité de confiance (comme une organisation reconnue).

Les certificats auto-signés sont créés par le propriétaire du site web lui-même, et non par une autorité de confiance. C'est comme si vous rédigiez votre propre carte d'identité au lieu d'en obtenir une auprès du gouvernement.

Votre navigateur affiche un avertissement car il ne peut pas vérifier si le certificat est fiable : il peut être légitime (comme avec les imprimantes EOS) ou potentiellement dangereux. Dans le cas des machines EOS, cela ne présente aucun danger car vous vous connectez à votre propre imprimante locale, et non à un site web aléatoire sur Internet.

Considérez cela comme suit : l'imprimante dit « Faites-moi confiance, je suis bien celle que je prétends être » sans qu'un tiers ne se porte garant pour elle.