You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 5 Next »

Akka.net 액터메시지를 통해  메시지를 라운드로빈 분산처리하고, 분산처리된 노드에 메시지가 갔을때

실제 아두이노 연결된 장비에 실제 램프를 켜는 변종실험을 해보겠습니다.

작동코드위치 : https://github.com/psmon/AkkaUno

아두이노란

아두이노란 물리적인 세계를 감지하고 제어할 수 있는 인터랙티브 객체들과 디지털 장치를 만들기 위한 도구로,

간단한 마이크로컨트롤러(Microcontroller) 보드를 기반으로 한 오픈 소스 컴퓨팅 플랫폼과 소프트웨어 개발 환경을 말합니다. 

다양한 장치들을 연결할수가 있으며, 여기서는 LED만제어하며  아두이노환경을 셋팅하는 부분은 생략하겠습니다.

시나리오구성

액터장치

  • ThrottleQueue : 100개의 메시지를 큐에 넣고 해당 메시지를 초당 하나씩 처리하여 , 라우터에 전달하여 분산처리되는 램프의 점등을 확인할수 있습니다.
  • RoundRobin : 메시지가 들어오면, 그룹으로 묶인 액트에게 순차적으로 메시지를 분배합니다.
  • WorkActor : 각 작업액트로 메시지가 들어오면, UnoActor에 LED를 켜고끄는 시그널을 전송합니다. 액트(n):LED(n) 으로 1:1연결됩니다. ( 시리얼 통신 )
  • UnoActor : 우노장비의 시리얼통신만 담당합니다. 받은 문자열을 그대로 장비에 전송합니다.



코드구현

기기에 임베디드되는 우노코드

시리얼포트로부터 문자가 오면, 해당문자에 연결된 LED를 켜고/끄는 코드입니다.

비교적 간단한 기기제어부분만 C++로 프로그래밍하고, 기기를 제어하는 이벤트발생 부분은 Akka.net으로 분리하였습니다.

가변저항을 이용한 LCD 문자열 출력코드가 같이 있지만 이부분은 무시해도 되겠습니다.(나중에 흐름속도제어를 저항장치로 연결예정입니다.)

#include <MsTimer2.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,16,2);
const int pinREG = A1;

//응용
int registerVal = -1;

int lampStart = 2;
int lampCount = 9;

float temperature;  
int reading;  
int lm35Pin = A0;

void setup() { 
  analogReference(INTERNAL);
  Serial.begin(9600);
  Serial.println("=========== app setup ===============");

  //pinMode(pinREG,INPUT);
  
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);

  lcd.init();  // LCD초기 설정
  lcd.backlight(); // LCD초기 설정
  lcd.setCursor(0,0);
  lcd.print("hello");  
  
  MsTimer2::set(100, onTimer);
  MsTimer2::start();  
  
}

void lampUpDown(int outPin){
  Serial.print("read :");
  Serial.println(outPin);
  int pinNum = outPin+1;    
  digitalWrite(outPin+1, HIGH);
}

void lampAllDown(){
  digitalWrite(2, LOW); 
  digitalWrite(3, LOW); 
  digitalWrite(4, LOW); 
  digitalWrite(5, LOW); 
  digitalWrite(6, LOW); 
  digitalWrite(7, LOW); 
  digitalWrite(8, LOW); 
  digitalWrite(9, LOW); 
  digitalWrite(10, LOW); 
}

void displayRegister(){  
  int curval = (analogRead(pinREG)/100)*100;
  if(curval!=registerVal){            
      lcd.setCursor(0,0);      
      lcd.print(curval);
      lcd.print(" :======");      
  }  
  registerVal = curval;
}

void serialLamp(){
  char input = Serial.read();

  switch(input){
    case '1':
      lampUpDown(1);
      break;
    case '2':
      lampUpDown(2);
      break;
    case '3':
      lampUpDown(3);
      break;
    case '4':
      lampUpDown(4);
      break;
    case '5':
      lampUpDown(5);
      break;
    case '6':
      lampUpDown(6);
      break;
    case '7':
      lampUpDown(7);
      break;
    case '8':
      lampUpDown(8);
      break;
    case '9':
      lampUpDown(9);
      break;
    default:
      lampAllDown();
      break;
  }
}

void loop() {
  displayRegister();
}

void onTimer(){
  serialLamp();
}


우노를 제어하는 액터코드

시리얼통신만 담당합니다.

using Akka.Actor;
using Akka.Event;
using System.IO.Ports;

namespace UnoAkkaApp.Actors
{
    public class UnoActor : ReceiveActor
    {
        private readonly ILoggingAdapter logger = Context.GetLogger();

        // 참고 : 리눅스의 경우 SerialPortStream 사용(현재 윈도우 지원)
        private readonly SerialPort arduSerialPort;        

        public UnoActor()
        {
            arduSerialPort = new SerialPort();
            arduSerialPort.PortName = "COM3";   //아두이노가 연결된 시리얼 포트 번호 지정
            arduSerialPort.BaudRate = 9600;     //시리얼 통신 속도 지정
            arduSerialPort.Open();              //포트 오픈

            ReceiveAsync<string>(async command =>
            {
                logger.Info($"Receive : {command}");
                arduSerialPort.Write(command);
            });
        }
    }
}


워크 액터

nodeNo는 자신의 노드번호에 해당하며, 작업발생시 위 UnoActor를 이용하여 각노드에연결된 LED에 시그널을 보냅니다.

using Akka.Actor;
using Akka.Event;
using AkkaDotModule.Models;

namespace UnoAkkaApp.Actors
{
    public class WorkActor : ReceiveActor
    {
        private readonly ILoggingAdapter logger = Context.GetLogger();

        private readonly IActorRef _unoActor;

        public WorkActor(IActorRef unoActor,int nodeNum)
        {
            _unoActor = unoActor;

            int _nodeNo = nodeNum;

            ReceiveAsync<BatchData>(async command =>
            {
                logger.Info($"Receive Data..");
                unoActor.Tell(nodeNum.ToString());
            });
        }
    }
}


액터를 조합하기 - 최종작동코드

            // start ActorSystem                        
            AkkaSystem = ActorSystem.Create("AkkaSystem");

            var unoActor = AkkaSystem.ActorOf(Props.Create(() => new UnoActor()), "unoActor");

            List<string> workActors = new List<string>();

            for (int i = 0; i < 9; i++)
            {
                string actorName = $"workActor{i + 1}";
                var curWorkActor = AkkaSystem.ActorOf(Props.Create(() => new WorkActor(unoActor, i + 1)), actorName);
                curWorkActor.Tell(new BatchData());
                workActors.Add($"/user/{actorName}");
            }

            // 참고 : https://getakka.net/articles/actors/routers.html            
            var router = AkkaSystem.ActorOf(Props.Empty.WithRouter(new RoundRobinGroup(workActors)), "roundRobinGroup");

            // 밸브 Work : 초당 작업량을 조절                
            int timeSec = 1;
            int elemntPerSec = 2;
            var throttleWork = AkkaSystem.ActorOf(Props.Create(() => new ThrottleWork(elemntPerSec, timeSec)), "throttleWork");
            // 밸브 작업자를 지정
            throttleWork.Tell(new SetTarget(router));

            //분산처리할 100개의 샘플데이터생성
            List<object> batchDatas = new List<object>();
            for (int i = 0; i < 100; i++)
            {
                batchDatas.Add(new BatchData() { Data="SomeData" });
            }
            BatchList batchList = new BatchList(batchDatas.ToImmutableList());

            //100개의 데이터전송(벌크)
            throttleWork.Tell(batchList);
  • UnoActor : 우노장비의 시리얼통신을 담당합니다.
  • ThrottleWork : 램프의 점등을 위해 속도를 제어합니다. 여기서의 출력은 분산처리를 위해 라운드 로빈으로 연결됩니다.
  • WorkActor : N개를 생성할수 있는 분산노드입니다. 이벤트가 발생하면 연결된 Led를 점등합니다. 우노장비에게 통신을 할수 있는 액터참조를 가지게됩니다.
  • RoundRobinGroup : N개의 WorkActor를 등록하여 분산처리될수 있게합니다.


실제 작동 시연 영상

akkauno-led.mp4


단일기기에 배포하기

우노보드는 개발장비에 연결하여, 개발할때 유용하며(개발장비와 USB연결)

단일기기 스탠드얼론으로 작동하게 하기위해서는  아두이노가 지원되면서 윈도우/리눅스 OS 선택가능한 - 닷넷코어구동가능

라떼판다를 통해 소형화가 가능합니다. 

우노장비

라떼판다


  • No labels