하드웨어 제품은 펌웨어를 업데이트하려면 유선으로 연결해야합니다. 소프트웨어 제품은 원격으로 붙어서 진행할 수 있지만 하드웨어는 그렇지 못해 번거로운 상황이 많이 생길 수 있습니다.
이런 문제를 해결하기 위한 OTA(Over The Air)
라는 펌웨어 업데이트 방식이 있는데, 이번 포스트에서는 OTA
를 구현해보겠습니다.
기능 특성상 펌웨어와 서버를 왔다갔다 하면서 진행해야하는데, 차례대로 따라하시면 쉽게 하실 수 있습니다.
ESP32
보드에 펌웨어를 업로드하기 위해서는 Arduino IDE
가 필요하고, 관련 패키지를 추가해줘야합니다.
JUST DOWNLOAD
버튼을 누르시면 됩니다.Arduino IDE > Preferences
메뉴로 들어가서 Additional boards manager URLs
에 https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json
를 적은 후 OK
를 누르면 ESP32
관련 패키지들이 자동으로 추가됩니다.Tools > Board > Boards Manager
메뉴로 들어가서 ESP32
를 검색하고 설치합니다.Arduino IDE
에서 보드와 포트를 선택합니다.ESP32
패키지에서 제공해주는 httpUpdate
예제코드를 사용하겠습니다.
File > Examples > HTTPUpdate > httpUpdate
를 선택하면 해당 코드가 IDE에 불러와집니다.setup()
함수 안에 Serial.println("v1");
을 추가해줍니다.SSID
와 PASSWORD
를 WiFiMulti.addAP()
함수에 입력해줍니다.loop()
함수 안에 추가하면 됩니다.OTA 서버
의 주소와 포트, 경로를 입력합니다. 이 부분은 뒤에서 서버구성을 완료한 후에 다시 진행하겠습니다.여기까지 해두고서 OTA 서버
를 구성하고 다시 돌아오겠습니다.
서버에서 할 일은 간단합니다. 펌웨어 업데이트 관련 라우터로 요청이 들어오면 요청에 맞는 펌웨어 파일을 전송해주는 역할이 전부입니다.
nest-cli
를 설치하고 새 프로젝트를 만듭니다.npm i -g @nestjs/cli
nest new ota-server
ota-server > src > main.ts
파일에 서버의 주소를 확인하기위한 코드를 작성합니다. 서버가 실행되면 현재의 주소를 콘솔에 출력해줍니다. 저의 경우에는 192.168.219.115
로 나왔고, 포트는 3000
번을 사용하겠습니다.// main.ts
import { networkInterfaces } from 'os';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const nets = networkInterfaces();
await app.listen(3000, () => {
const results = {};
for(let name of Object.keys(nets)) {
for(let net of nets[name]) {
if(net.family === 'IPv4' && !net.internal) {
if(!results[name]) {
results[name] = [];
}
results[name].push(net.address);
}
}
}
console.log(`Listening on port ${3000}\n${JSON.stringify(results, null, ' ')}`);
});
}
bootstrap();
다시 펌웨어로 돌아가 서버 정보를 설정하겠습니다.
위의 펌웨어 작성 5번 항목에 필요한 정보를 얻었으니, 이제 펌웨어 코드를 완성할 수 있습니다. OTA 서버
를 실행해서 얻은 주소와 포트를 적어줍니다. 경로는 /firmware
로 하겠습니다.
해당 값들을 httpUpdate.update()
함수에 적어주시면 됩니다.
펌웨어 코드를 다 작성하고나서 Arduino IDE
왼쪽 상단에 있는 Upload
버튼을 눌러 보드에 업로드를 해줍니다.
업로드가 안되면
Tools > Upload Speed
메뉴에서Baud Rate
를115200
으로 바꿔보세요.
업로드가 완료되면 현재 펌웨어 버전을 확인할 수 있습니다.
지금은 OTA 서버
가 실행중이 아니기때문에 connection refused
에러가 발생합니다.
시리얼 로그는
Arduino IDE
오른쪽 상단에 있는Serial Monitor
버튼을 눌러서 확인할 수 있습니다.
OTA 서버
에 업로드해둘 펌웨어 파일을 생성합니다.
1. 기존 펌웨어와 차이점을 두기 위해 펌웨어 버전을 v2
로 수정합니다.
2. Sketch > Export Compiled Binary
메뉴를 선택해서 새로운 펌웨어 파일을 생성합니다.
3. Sketch > Show Sketch Folder
메뉴를 선택해서 펌웨어 파일이 있는 폴더를 열어줍니다.
4. build > esp32.esp32.d32 > httpUpdate.ino.bin
파일을 OTA 서버 프로젝트 > firmware
폴더로 복사합니다.
펌웨어에서 설정한대로 /firmware
경로로 접속했을 때 펌웨어 파일을 다운받을 수 있도록 코드를 작성해보겠습니다. src/app.controller.ts
파일에서 진행합니다.
구현 방식은 Express
방식과 NestJS
방식으로 두 가지가 있습니다.
import * as path from 'path';
import { statSync, createReadStream } from 'fs';
import { Controller, Get, Res } from '@nestjs/common';
import { Response } from 'express';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('/firmware')
getFirmware(@Res() res: Response) {
console.log('Received the firmware download request');
const filePath = path.join(process.cwd(), 'firmware/httpUpdate.ino.bin');
const fileStat = statSync(filePath);
const fileStream = createReadStream(filePath);
res.set({
'Content-Length': fileStat.size,
});
fileStream.pipe(res);
}
}
import * as path from 'path';
import { statSync, createReadStream } from 'fs';
import { Controller, Get, Res, StreamableFile } from '@nestjs/common';
import { Response } from 'express';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('/firmware')
getFirmware(@Res({ passthrough: true }) res: Response): StreamableFile {
console.log('Received the firmware download request');
const filePath = path.join(process.cwd(), 'firmware/httpUpdate.ino.bin');
const fileStat = statSync(filePath);
const fileStream = createReadStream(filePath);
res.set({
'Content-Length': fileStat.size,
});
return new StreamableFile(fileStream);
}
}
주의해야할 점은
Header
에Content-Length
를 같이 보내주어야 합니다. 그렇지않으면premature close
라는 에러가 발생합니다.
브라우저에서 localhost:3000/firmware
로 접속해보면 펌웨어 파일이 다운로드되는 것을 확인할 수 있습니다.
이제 보드를 연결하면 자동으로 펌웨어를 다운로드받고 업데이트를 진행합니다.
펌웨어 버전이 v2
로 올라간 것을 확인할 수 있습니다.
OTA
에 대한 글들은 대부분 Express
를 기반으로 작성한 경우가 많아서 이번에 NestJS
로 개발을 진행하며 작성해보았습니다. 하드웨어, 펌웨어 개발도 오랜만에 해보니 재밌는 것 같네요.