매직미러에 학교급식 정보 띄우기: 구현 가이드
매직미러에 학교급식 정보를 표시하는 방법. NEIS API 키 발급과 급식 데이터 수신부터 매직미러 모듈 제작 및 설치까지 단계별로 설명된 가이드. 학생들에게 급식 정보를 실시간으로 제공하는 방법.

매직미러라고 있다.
이것저것 정보들을 보여주는 대시보드? 같은건데
원래는 미러필름을 붙여서 스마트미러로 쓰는거지만 나는 그냥 모니터에 띄우려고 한다.
작년에 학교에서 전자게시판에 띄우려고 만들다가 다 못만들어서 새로 시작할 것 이다.
이번에는 꼭 완성해서 학교에 띄워놔야지 ㅎㅎ
원래는 라즈베리파이에 리눅스 깔고 nodejs 설치해서 매직미러를 올렸지만 이번에는 그냥 도커에다가 올려서 쉽게 시작했다.

뭔가 허전하다. 학생이라면 학교에서 가장 중요한 것이 급식 아니겠는가?
그래서 학교 급식을 넣으려고 찾아보았다. 그런데 아무도 급식 뜨게하는 모듈은 만들어놓지 않았다...
결국 내가 만들기로 했다 ㅎㅎ

다행히도 open api에 학교별 급식을 올려주는 api가 있었다. 그래서 이걸 이용해서 만들어 보려고 한다.
먼저 api키를 발급받아주었다.
그 다음 급식식단 api를 사용하는 방법에 대해 알아보았다.
나는 웹형식으로 구현할 것이기 때문에 request로 api를 요청할 것이다.
요청시에는 여러 호출 인자들이 필요하다
내 api키, 학교의 교육청코드, 학교명코드, 날짜정도가 된다.
아래의 api 요청 url을 살펴보자
https://open.neis.go.kr/hub/mealServiceDietInfo?KEY=내 api 키&Type=json&plndex=1&pSize=30&ATPT_OFCDC_SC_CODE=교육청 코드&SD_SCHUL_CODE=학교 코드&MLSV_YMD=날짜 로 구성된다.
실체로 호출을 한 다음 접속해보면

이렇게 정보를 받을 수 있다.
이제 많은 정보들 중 필요한 정보만 도려내야 한다.
따라서 코드에는 문자열을 파싱하는 부분도 포함해야 한다.
나는 조식 중식 석식 메뉴들와 칼로리정보를 받을 것이다.
그럼 정리를 해보자
매직미러에서 급식 화면을 띄우기 위해서는 api 호출, 급식내용 수신, 문자열 파싱, 정보 표시로 구성된다.
이제 구현을 해보자
코드 부분이 정말 길지만
나중에 내가 다시 확인하기 위해서 올려놓겠다.
Module.register("MMM-SchoolMeals", {
defaults: {
},
start: function() {
this.meals = [];
this.getMeals();
// 매일 자정에 급식 정보 업데이트
var self = this;
var refreshSchedule = function() {
var now = new Date();
var millisTillMidnight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 24, 0, 0, 0) - now;
if (millisTillMidnight < 0) {
millisTillMidnight += 86400000;
}
setTimeout(function() {
self.getMeals();
refreshSchedule();
}, millisTillMidnight);
};
refreshSchedule();
},
getMeals: function() {
this.sendSocketNotification("GET_MEALS", this.config);
},
getScripts: function() {
return [];
},
getStyles: function() {
return [];
},
getDom: function() {
var wrapper = document.createElement("table");
wrapper.className = "small table";
var headerRow = document.createElement("tr");
headerRow.innerHTML = "<th>조식</th><th>중식</th><th>석식</th>";
wrapper.appendChild(headerRow);
var mealsRow = document.createElement("tr");
var caloriesRow = document.createElement("tr");
var mealsTypes = { "조식": "급식 없음", "중식": "급식 없음", "석식": "급식 없음" };
var caloriesTypes = { "조식": "", "중식": "", "석식": "" };
this.meals.forEach(meal => {
var mealName = meal.MMEAL_SC_NM;
var mealInfo = meal.DDISH_NM.trim() === "" ? "급식 없음" : meal.DDISH_NM.replace(/\([^)]*\)/g, '').replace(/\s+/g, ', ');
var calInfo = meal.CAL_INFO || "";
mealsTypes[mealName] = mealInfo;
caloriesTypes[mealName] = calInfo;
});
mealsRow.innerHTML += `<td>${mealsTypes["조식"]}</td>`;
mealsRow.innerHTML += `<td>${mealsTypes["중식"]}</td>`;
mealsRow.innerHTML += `<td>${mealsTypes["석식"]}</td>`;
caloriesRow.innerHTML += `<td>${caloriesTypes["조식"]}</td>`;
caloriesRow.innerHTML += `<td>${caloriesTypes["중식"]}</td>`;
caloriesRow.innerHTML += `<td>${caloriesTypes["석식"]}</td>`;
wrapper.appendChild(mealsRow);
wrapper.appendChild(caloriesRow);
return wrapper;
}
,
socketNotificationReceived: function(notification, payload) {
if (notification === "MEALS_RESULT") {
this.meals = payload;
this.updateDom();
}
}
});
MMM-SchoolMeals.js
var NodeHelper = require("node_helper");
var request = require("request");
module.exports = NodeHelper.create({
start: function() {
console.log("MMM-SchoolMeals helper started...");
},
socketNotificationReceived: function(notification, payload) {
if (notification === "GET_MEALS") {
this.getMeals(payload);
}
},
getMeals: function(config) {
var url = "https://open.neis.go.kr/hub/mealServiceDietInfo";
// 현재 날짜를 yyyyMMdd 형식으로 설정
var today = new Date();
var yyyy = today.getFullYear();
var mm = String(today.getMonth() + 1).padStart(2, '0');
var dd = String(today.getDate()).padStart(2, '0');
var formattedToday = yyyy + mm + dd;
var requestUrl = `${url}?KEY=${config.apiKey}&Type=json&plndex=1&pSize=30&ATPT_OFCDC_SC_CODE=${config.atptOfcdcScCode}&SD_SCHUL_CODE=${config.sdSchulCode}&MLSV_YMD=${formattedToday}`;
request(requestUrl, (error, response, body) => {
if (!error && response.statusCode == 200) {
var result = JSON.parse(body).mealServiceDietInfo[1].row;
this.sendSocketNotification("MEALS_RESULT", result);
}
});
}
});
결과는 아래처럼 나온다
