bonjour a tous!
savez vous s’il existe une app qui va chercher les horaires de trains sans l’API SNCF comme nous l’avions sur Jeedom?
cette application me manque…
merci beaucoup!!!
bonjour a tous!
savez vous s’il existe une app qui va chercher les horaires de trains sans l’API SNCF comme nous l’avions sur Jeedom?
cette application me manque…
merci beaucoup!!!
J’ai également cherché, sans succès !
Ce serait super d’être prévenu du retard de son train le matin avant d’aller au boulot !
oui la communauté francaise d’homey est beaucoup plus petite que celle de jeedom…
dommage
J’ai déjà du mal à récupérer un accès à l’API…
J’ai fait une demande ici sans succès pour le moment…
Un petit script pour afficher sur une horloge awtrix les horraires de départ ou d’arrivé d’un train à une gare precise
J’ai pas encore tout tester mais si qqun veut m’aider…
Hesitez pas à adapter si vous voulez utiliser autre chose que l’awtrix
Quand le script sera fiable, j’en ferais une app
const apiKey = 'XXXX-XXXXX-XXXXX-XXXXX'; // 🔒 Remplace par ta clé API SNCF a récuperer sur https://numerique.sncf.com/startup/api/token-developpeur/
const maxResults = 40; // Nombre de départs à charger
const gares = {//A completer
"VERPILLIERE": 'stop_area:SNCF:87723395',
"PART_DIEU": 'stop_area:SNCF:87723197',
"ST_EXPUERY": 'stop_area:SNCF:87762906'
};
const deviceId = 'YYYYYYYYYY'; // ID DE AWTRIX
const device = await Homey.devices.getDevice({ id: deviceId });
const ipValue = device.capabilitiesObj['ip']?.value;//IP de AWTRIX
function toISO(str) {
return str.slice(0,4) + '-' + str.slice(4,6) + '-' + str.slice(6,8) + 'T' +
str.slice(9,11) + ':' + str.slice(11,13) + ':' + str.slice(13,15) + 'Z';
}
const options = {
method: 'GET',
headers: {
'Authorization': apiKey
}
};
async function getDataForUrl(url) {
try {
const response = await fetch(url, options);
if (!response.ok) throw new Error(`Erreur API SNCF : ${response.status} ${response.statusText}`);
return await response.json();
} catch (error) {
throw new Error('Erreur réseau : ' + error.message);
}
}
async function getJourneyInfos(trainNumber, stopArea){
const urlJourney= `https://api.sncf.com/v1/coverage/sncf/vehicle_journeys?headsign=${trainNumber}`;
const journeyDatas = await getDataForUrl(urlJourney);
const journeys = journeyDatas.vehicle_journeys || [];
const disruptionDetails = journeyDatas.disruptions || [];
const rawNow = journeyDatas.context.current_datetime;
const rawDayNow = rawNow.split('T')[0];
const rawDateNow = `${rawDayNow.slice(0, 4)}-${rawDayNow.slice(4, 6)}-${rawDayNow.slice(6, 8)}`;
for (const journey of journeys) {
var journeyDay = journey.id.split(':')[2];
if( journeyDay != rawDateNow) continue;
const stops = journey.stop_times;
if (!stops || stops.length === 0) continue;
const matchingStop = stops.find(stop => {
var stopPointId = stop.stop_point?.id;
var stopPointIdParts = stopPointId.split(':');
var stopAreaParts = stopArea.split(':');
return `${stopPointIdParts[1]}:${stopPointIdParts[2]}` === `${stopAreaParts[1]}:${stopAreaParts[2]}`;
});
if (!matchingStop) break;
const departureTimeAtStopPoint = matchingStop.departure_time;
const arrivalTimeAtStopPoint = matchingStop.arrival_time;
if (journey.disruptions.length === 0) {
return {
'responseStatus': "SUCCESS",
'contexteTime': rawNow,
'journeyId': trainNumber,
'journeyStartTime': journeyDay.replaceAll('-', '')+"T"+stops[0].departure_time,
'baseStopPointDeparture': journeyDay.replaceAll('-', '')+"T"+departureTimeAtStopPoint,
'baseStopPointArrival':journeyDay.replaceAll('-', '')+"T"+arrivalTimeAtStopPoint,
'disruption': 'NO_DISRUPTION'
};
}
const journeyDistruptionsIds = [];
for( const distr of journey.disruptions){
journeyDistruptionsIds.push(distr.id);
}
// Rechercher les détails des disruptions liées
const relatedDisruptions = disruptionDetails
.map(d => d.id)
.map(id => disruptionDetails.find(dd => dd.id === id))
.filter(Boolean); // supprimer les null
const hasCancellation = relatedDisruptions.some(disruption => {
if( !journeyDistruptionsIds.includes(disruption.id)) return false;
const effect = disruption?.severity?.effect?.toUpperCase() || "";
return effect === "NO_SERVICE";
});
if (hasCancellation) {
return {
'responseStatus': "SUCCESS",
'contexteTime': rawNow,
'journeyId': trainNumber,
'journeyStartTime': journeyDay.replaceAll('-', '')+"T"+stops[0].departure_time,
'baseStopPointDeparture': journeyDay.replaceAll('-', '')+"T"+departureTimeAtStopPoint,
'baseStopPointArrival':journeyDay.replaceAll('-', '')+"T"+arrivalTimeAtStopPoint,
'disruption': 'CANCELLED'
};
}else {
return {
'responseStatus': "SUCCESS",
'contexteTime': rawNow,
'journeyId': trainNumber,
'journeyStartTime': journeyDay.replaceAll('-', '')+"T"+stops[0].departure_time,
'baseStopPointDeparture': journeyDay.replaceAll('-', '')+"T"+departureTimeAtStopPoint,
'baseStopPointArrival':journeyDay.replaceAll('-', '')+"T"+arrivalTimeAtStopPoint,
'disruption': 'HAS_DISRUPTIONS'
};
}
}
return {
'responseStatus': "ERROR",
'journeyId': trainNumber
};
}
async function sendMessageToAwTrix(waringMessage, appID, msg, lifeTime){
this.log('Envoie du message: '+msg);
this.log('Durée du message: '+lifeTime);
const urlAwtrix = 'http://'+ipValue+'/api/custom?name='+appID;
const ttlAwtrix = lifeTime;//Math.round((timeReal - timeContexte) / 1000);
const payload = {
"text": msg,
"repeat": 3,
"icon":waringMessage?"alertwarning":"train",
"lifetime": ttlAwtrix
};
try {
const response = await fetch(urlAwtrix, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error) {
console.error('Erreur lors du POST:', error);
}
}
async function computeAndSendInfosForDepartureJourney(journeyInfos, stopArea){
const rawContextDateTime = journeyInfos.contexteTime;
const timeContexte = new Date(toISO(rawContextDateTime));
const rawBaseDateTime = journeyInfos.baseStopPointDeparture;
const timeBase = new Date(toISO(rawBaseDateTime));
const hours = timeBase.getUTCHours().toString().padStart(2, '0');
const minutes = timeBase.getUTCMinutes().toString().padStart(2, '0');
const rawHourBase = `${hours}:${minutes}`
if (journeyInfos.disruption == "CANCELLED" && timeBase > timeContexte) {
sendMessageToAwTrix(true, "train"+journeyInfos.journeyId,`${rawHourBase} SUPPRIME`, Math.round((timeBase - timeContexte ) / 1000))
return;
}
const urlStopPointInfo = `https://api.sncf.com/v1/coverage/sncf/stop_areas/${stopArea}/departures?count=${maxResults}`;
const data = await getDataForUrl(urlStopPointInfo);
// Trouve le train par son numéro exact
const train = data.departures.find(dep => {
const tripNum = dep.display_informations?.trip_short_name || '';
return tripNum === journeyInfos.journeyId;
});
if (!train){
return;
}
let messageFinal = rawHourBase;
const rawRealDateTime = train.stop_date_time?.departure_date_time;
const timeReal = new Date(toISO(rawRealDateTime));
var delaySec = 0;
if( rawBaseDateTime != rawRealDateTime){
delaySec = Math.round((timeReal - timeBase) / 1000);
}
const delayMin = Math.round(delaySec / 60);
if (delayMin > 0){
messageFinal += " +"+delayMin+" MN";
}else{
messageFinal += " A L'HEURE";
}
sendMessageToAwTrix( delayMin > 0, "train"+journeyInfos.journeyId, messageFinal, Math.round((timeReal - timeContexte) / 1000))
}
async function computeAndSendInfosForArrivalJourney(journeyInfos, stopArea){
const rawContextDateTime = journeyInfos.contexteTime;
const timeContexte = new Date(toISO(rawContextDateTime));
const rawBaseDateTimeArrival = journeyInfos.baseStopPointArrival;
const timeBaseArrival = new Date(toISO(rawBaseDateTimeArrival));
const hoursArrival = timeBaseArrival.getUTCHours().toString().padStart(2, '0');
const minutesArrival = timeBaseArrival.getUTCMinutes().toString().padStart(2, '0');
const rawHourBaseArrival = `${hoursArrival}:${minutesArrival}`
if (journeyInfos.disruption == "CANCELLED" && timeBaseArrival > timeContexte) {
sendMessageToAwTrix(true, "train"+journeyInfos.journeyId,`${rawHourBaseArrival} SUPPRIME`, Math.round((timeBase - timeContexte ) / 1000))
return;
}
const urlStopPointInfo = `https://api.sncf.com/v1/coverage/sncf/stop_areas/${stopArea}/arrivals?count=${maxResults}`;
const data = await getDataForUrl(urlStopPointInfo);
// Trouve le train par son numéro exact
const train = data.arrivals.find(dep => {
const tripNum = dep.display_informations?.trip_short_name || '';
return tripNum === journeyInfos.journeyId;
});
if (!train){
return;
}
let messageFinal = rawHourBaseArrival;
const rawRealDateTimeArrival = train.stop_date_time?.arrival_date_time;
const timeRealArrival = new Date(toISO(rawRealDateTimeArrival));
var delaySec = 0;
if( rawBaseDateTimeArrival != rawRealDateTimeArrival){
delaySec = Math.round((timeRealArrival - timeBaseArrival) / 1000);
}
const delayMin = Math.round(delaySec / 60);
if (delayMin > 0){
messageFinal += " +"+delayMin+" MN";
}else{
messageFinal += " A L'HEURE";
}
sendMessageToAwTrix( delayMin > 0, "train"+journeyInfos.journeyId, messageFinal, Math.round((timeRealArrival - timeContexte) / 1000))
}
/*Usage exemple
const jsonArgs = {
"mode": "ARRIVAL",
"stopPoint": "PART_DIEU",
"trains": ["6669", "875718"]
};
const jsonArgs = {
"mode": "DEPARTUR",
"stopPoint": "PART_DIEU",
"trains": ["6669", "875718"]
};*/
const jsonArgs = JSON.parse(args[0]);
const mode = jsonArgs.mode;
for (const trainNumber of jsonArgs.trains) {
const journeyInfos= await getJourneyInfos(trainNumber, gares[jsonArgs.stopPoint]);
if( !journeyInfos || journeyInfos.responseStatus == "ERROR")
return;
if( mode == "DEPARTURE")
computeAndSendInfosForDepartureJourney(journeyInfos, gares[jsonArgs.stopPoint]);
else if( mode == "ARRIVAL"){
computeAndSendInfosForArrivalJourney(journeyInfos, gares[jsonArgs.stopPoint]);
}
}