๐Ÿ˜Ž ๊ณต๋ถ€ํ•˜๋Š” ์ง•์ง•์•ŒํŒŒ์นด๋Š” ์ฒ˜์Œ์ด์ง€?

[๋ฆฌ๋ˆ…์Šค ๋„คํŠธ์›Œํฌ ํ”„๋กœ๊ทธ๋ž˜๋ฐ] ์›น ์„œ๋ฒ„์™€ ๋ผ์ฆˆ๋ฒ ๋ฆฌ ํŒŒ์ด์˜ ์ œ์–ด ๋ณธ๋ฌธ

๐Ÿ‘ฉ‍๐Ÿ’ป IoT (Embedded)/Raspberry Pi

[๋ฆฌ๋ˆ…์Šค ๋„คํŠธ์›Œํฌ ํ”„๋กœ๊ทธ๋ž˜๋ฐ] ์›น ์„œ๋ฒ„์™€ ๋ผ์ฆˆ๋ฒ ๋ฆฌ ํŒŒ์ด์˜ ์ œ์–ด

์ง•์ง•์•ŒํŒŒ์นด 2024. 2. 13. 00:15
728x90
๋ฐ˜์‘ํ˜•

<์„œ์˜์ง„ ๋‹˜์˜ ์‚ฌ๋ฌผ์ธํ„ฐ๋„ท์„ ์œ„ํ•œ ๋ฆฌ๋ˆ…์Šค ํ”„๋กœ๊ทธ๋ž˜๋ฐ with ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด ์„œ์ ์„ ์ฐธ๊ณ ํ•ด์„œ ์ž‘์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค :-)>

 

โญ ์›น ์„œ๋ฒ„์™€ ๋ผ์ฆˆ๋ฒ ๋ฆฌ ํŒŒ์ด์˜ ์ œ์–ด

HTML ๋ฌธ์„œ ์•ˆ์— ์˜จ๋„์™€ ์Šต๋„ ๋“ฑ์„ ์ง์ ‘ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ๋„๋ก ์‚ฝ์ž…ํ•จ + lED ์ผœ๊ณ  ๋Œ ์ˆ˜ ์žˆ๋Š” ๋ผ๋””์˜ค ๋ฒ„ํŠผ + ์Œ์•…์„ ์—ฐ์ฃผํ•˜๊ณ  ๋ชจํ„ฐ๋ฅผ ์ œ์–ด
์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ์›น ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ผ ๋•Œ <FORM> ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค
<FORM> ํƒœ๊ทธ๋Š” POST ๋ฉ”์‹œ์ง€๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด์ง€๋งŒ, ์•ž ์›น ์„œ๋ฒ„๋Š” GET ๋ฉ”์†Œ๋“œ๋งŒ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์—
<FORM> ํƒœ๊ทธ์˜ method ์†์„ฑ์„ ์ด์šฉํ•˜์—ฌ GET ๋ฉ”์†Œ๋“œ๋กœ ๋ณด๋‚ด๋„๋ก ์„ค์ •ํ•œ๋‹ค

์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ์›น ํŽ˜์ด์ง€๋ฅผ ๋ถˆ๋Ÿฌ๋“ค์—ฌ์„œ ์˜ต์…˜์„ ์„ ํƒํ•˜๊ณ  Submit ๋ฒ„ํŠผ์„ ์„ ํƒํ•˜๋ฉด, ์›น ์„œ๋ฒ„๋กœ 
'http://์„œ๋ฒ„์ฃผ์†Œ:ํฌํŠธ๋ฒˆํ˜ธ/index.html?led=Off' ์š”์ฒญ์˜ GET ๋ฉ”์†Œ๋“œ๋กœ ์ „๋‹ฌ๋œ๋‹ค
์›น ์„œ๋ฒ„์—์„œ๋Š” GET ๋ฉ”์†Œ๋“œ๋กœ ์ „์†ก๋œ ๋ฉ”์‹œ์ง€๋ฅผ ๋ถ„์„ํ•˜๊ณ  ๋ผ์ฆˆ๋ฒ ๋ฆฌ ํŒŒ์ด์˜ GPIO ์— ํ•ด๋‹น ๋ช…๋ น์„ ๋‚ด๋ฆด ์ˆ˜ ์žˆ๋‹ค

์ฝ”๋“œ๋ฅผ ๋นŒ๋“œ ์‹œ Pthread ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, wiringPi ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ชจ๋‘ ๋งํฌํ•ด์•ผ ํ•œ๋‹ค
์“ฐ๋‹ˆ๋Š” ์˜จ์Šต๋„ ์ธก์ • ์„ผ์„œ๊ฐ€ ์—†์–ด์„œ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค ใ… 

// rpi6.c
// PC ์—์„œ ๋ผ์ฆˆ๋ฒ ๋ฆฌ ํŒŒ์ด๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <termios.h>
#include <wiringPi.h>
#include <wiringPiI2C.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <linux/input.h>

static const char* I2C_DEV = "/dev/i2c-1"; 	/* I2C๋ฅผ ์œ„ํ•œ ์žฅ์น˜ ํŒŒ์ผ */
static const int I2C_SLAVE = 0x0703; 		/* ioctl() ํ•จ์ˆ˜์—์„œ I2C_SLAVE ์„ค์ •์„ ์œ„ํ•œ ๊ฐ’ */

static const int LPS25H_ID = 0x5C; 		/* SenseHAT์˜ I2C-1์˜ ๊ฐ’ */
static const int HTS221_ID = 0x5F;

static const int CTRL_REG1 = 0x20; 		/* STMicroelectronics์˜ ์ŠคํŽ™ ๋ฌธ์„œ์˜ ๊ฐ’ */
static const int CTRL_REG2 = 0x21;

static const int PRESS_OUT_XL = 0x28; 		/* STMicroelectronics์˜ LPS25H ์ŠคํŽ™ ๋ฌธ์„œ์˜ ๊ฐ’ */
static const int PRESS_OUT_L = 0x29;
static const int PRESS_OUT_H = 0x2A;
static const int PTEMP_OUT_L = 0x2B;
static const int PTEMP_OUT_H = 0x2C;

static const int H0_T0_OUT_L = 0x36; 		/* STMicroelectronics์˜ HTS221 ์ŠคํŽ™ ๋ฌธ์„œ์˜ ๊ฐ’ */
static const int H0_T0_OUT_H = 0x37;
static const int H1_T0_OUT_L = 0x3A;
static const int H1_T0_OUT_H = 0x3B;
static const int H0_rH_x2 = 0x30;
static const int H1_rH_x2 = 0x31;

static const int H_T_OUT_L = 0x28;
static const int H_T_OUT_H = 0x29;

static const int T0_OUT_L = 0x3C;
static const int T0_OUT_H = 0x3D;
static const int T1_OUT_L = 0x3E;
static const int T1_OUT_H = 0x3F;
static const int T0_degC_x8 = 0x32;
static const int T1_degC_x8 = 0x33;
static const int T1_T0_MSB = 0x35;

static const int TEMP_OUT_L = 0x2A;
static const int TEMP_OUT_H = 0x2B;

/* ์Šค๋ ˆ๋“œ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฎคํ…์Šค */
static pthread_mutex_t pressure_lock;
static pthread_mutex_t temperature_lock;

static int is_run = 1; 				/* ์Šค๋ ˆ๋“œ ์ข…๋ฃŒ๋ฅผ ์œ„ํ•œ ํ”Œ๋ž˜๊ทธ */
int pressure_fd, temperature_fd;

int kbhit(void);

/* ์Šค๋ ˆ๋“œ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ํ•จ์ˆ˜ */
void* webserverFunction(void* arg);
static void *clnt_connection(void *arg);
int sendData(FILE* fp, char *ct, char *filename);
void sendOk(FILE* fp);
void sendError(FILE* fp);

void getPressure(int fd, double *temperature, double *pressure); /* ๊ธฐ์••๊ณผ ์˜จ๋„๋ฅผ ์œ„ํ•œ ํ•จ์ˆ˜ */
void getTemperature(int fd, double *temperature, double *humidity); /* ์˜จ๋„์™€ ์Šต๋„๋ฅผ ์œ„ํ•œ ํ•จ์ˆ˜ */

void *pressureFunction(void *arg)
{
    double t_c = 0.0; /* ์˜จ๋„์™€ ์••๋ ฅ์„ ์ถœ๋ ฅํ•˜๊ธฐ ์œ„ํ•œ ๋ณ€์ˆ˜ */
    double pressure = 0.0;
    while(is_run) {
        if(pthread_mutex_trylock(&pressure_lock) != EBUSY) { 	/* ์ž„๊ณ„ ๊ตฌ์—ญ ์„ค์ • */
            /* LPS25H ์žฅ์น˜ ์ดˆ๊ธฐํ™” */
            wiringPiI2CWriteReg8(pressure_fd, CTRL_REG1, 0x00);
            wiringPiI2CWriteReg8(pressure_fd, CTRL_REG1, 0x84);
            wiringPiI2CWriteReg8(pressure_fd, CTRL_REG2, 0x01);

            getPressure(pressure_fd, &t_c, &pressure);
            printf("Temperature(from LPS25H) = %.2f°C\n", t_c);
            printf("Pressure = %.0f hPa\n", pressure);

            pthread_mutex_unlock(&pressure_lock); 		/* ์ž„๊ณ„ ๊ตฌ์—ญ ํ•ด์ œ */
        }
        delay(1000);
    }
    return NULL;
}

void *temperatureFunction(void *arg)
{
    double temperature, humidity;
    while(is_run) {
        if(pthread_mutex_trylock(&temperature_lock) != EBUSY) { /* ์ž„๊ณ„ ๊ตฌ์—ญ ์„ค์ • */
	    /* HTS221 ์žฅ์น˜ ์ดˆ๊ธฐํ™” */
	    wiringPiI2CWriteReg8(temperature_fd, CTRL_REG1, 0x00);
	    wiringPiI2CWriteReg8(temperature_fd, CTRL_REG1, 0x84);
	    wiringPiI2CWriteReg8(temperature_fd, CTRL_REG2, 0x01);

            getTemperature(temperature_fd, &temperature, &humidity);
            printf("Temperature(from HTS221) = %.2f°C\n", temperature);
            printf("Humidity = %.0f%% rH\n", humidity);
            pthread_mutex_unlock(&temperature_lock); 		/* ์ž„๊ณ„ ๊ตฌ์—ญ ํ•ด์ œ */
        }
        delay(1000);
    }
    return NULL;
}

void* joystickFunction(void* arg)
{
    int fd;
    struct input_event ie;
    char* joy_dev = "/dev/input/event10";
    if((fd = open(joy_dev, O_RDONLY)) == -1) {
        perror("opening device");
        return NULL;
    }
    while(is_run) {
        read(fd, &ie, sizeof(struct input_event));
        printf("time %ld.%06ld\ttype %d\tcode %-3d\tvalue %d\n",
                ie.time.tv_sec, ie.time.tv_usec, ie.type, ie.code,
                ie.value);
        if(ie.type) {
            switch(ie.code) {
                case KEY_UP: printf("Up\n"); break;		/* 'Up' ๋ฐฉํ–ฅํ‚ค: ์กฐ์ด์Šคํ‹ฑ 'Up' */
                case KEY_DOWN: printf("Down\n"); break;		/* 'Down' ๋ฐฉํ–ฅํ‚ค: ์กฐ์ด์Šคํ‹ฑ 'Down' */
                case KEY_LEFT: printf("Left\n"); break;		/* 'Left' ๋ฐฉํ–ฅํ‚ค: ์กฐ์ด์Šคํ‹ฑ 'Left' */
                case KEY_RIGHT: printf("Right\n"); break;	/* 'Right' ๋ฐฉํ–ฅํ‚ค: ์กฐ์ด์Šคํ‹ฑ 'Right' */
                case KEY_ENTER: 				/* ‘Enter’ ํ‚ค: ์กฐ์ด์Šคํ‹ฑ ‘Press’ */
		    printf("Enter\n"); is_run = 0; break; 	
                default: printf("Default\n"); break; 		/* ์œ„์˜ ํ‚ค๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ */
            }
        }
        delay(100);
    }

    return NULL;
}

int main(int argc, char **argv)
{
    int i = 0;
    pthread_t ptPressure, ptTemperature, ptJoystick, ptWebserver;
    pthread_mutex_init(&pressure_lock, NULL); 		/* ๋ฎคํ…์Šค ์ดˆ๊ธฐํ™” */
    pthread_mutex_init(&temperature_lock, NULL);

    /* ํ”„๋กœ๊ทธ๋žจ์„ ์‹œ์ž‘ํ•  ๋•Œ ์„œ๋ฒ„๋ฅผ ์œ„ํ•œ ํฌํŠธ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅ๋ฐ›๋Š”๋‹ค. */
    if(argc!=2) {
        printf("usage: %s <port>\n", argv[0]);
        return -1;
    }

    /* I2C ์žฅ์น˜ ํŒŒ์ผ์„ ์˜คํ”ˆ */
    if((pressure_fd = open(I2C_DEV, O_RDWR)) < 0) {
        perror("Unable to open i2c device");
        return 1;
    }

    /* I2C ์žฅ์น˜๋ฅผ ์Šฌ๋ ˆ์ด๋ธŒ(slave) ๋ชจ๋“œ๋กœ LPS25H๋ฅผ ์„ค์ • */
    if(ioctl(pressure_fd, I2C_SLAVE, LPS25H_ID) < 0) {
        perror("Unable to configure i2c slave device");
        close(pressure_fd);
        return 1;
    }

    /* I2C ์žฅ์น˜ ํŒŒ์ผ์„ ์˜คํ”ˆ */
    if((temperature_fd = open(I2C_DEV, O_RDWR)) < 0) {
        perror("Unable to open i2c device");
        return 1;
    }

    /* I2C ์žฅ์น˜๋ฅผ ์Šฌ๋ ˆ์ด๋ธŒ(slave) ๋ชจ๋“œ๋กœ HTS221๋ฅผ ์„ค์ • */
    if(ioctl(temperature_fd, I2C_SLAVE, HTS221_ID) < 0) {
        perror("Unable to configure i2c slave device");
        close(temperature_fd);
        return 1;
    }

//    pthread_create(&ptPressure, NULL, pressureFunction, NULL);
//    pthread_create(&ptTemperature, NULL, temperatureFunction, NULL);
    pthread_create(&ptJoystick, NULL, joystickFunction, NULL);
    pthread_create(&ptWebserver, NULL, webserverFunction, (void*)(atoi(argv[1])));

    printf("q : Quit\n");
    for(i = 0; is_run;i++) {
        if(kbhit()) { 			/* ํ‚ค๋ณด๋“œ๊ฐ€ ๋ˆŒ๋ ธ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. */
            switch(getchar()) { 	/* ๋ฌธ์ž๋ฅผ ์ฝ๋Š”๋‹ค. */
	        case 'q': 		/* ์ฝ์€ ๋ฌธ์ž๊ฐ€ q์ด๋ฉด ์ข…๋ฃŒํ•œ๋‹ค. */
		    pthread_kill(ptWebserver, SIGINT);
		    pthread_cancel(ptWebserver);
		    is_run = 0;
		    goto END;
            };
        }

    //    printf("%20d\t\t\r", i); 	/* ํ˜„์žฌ ์นด์šดํŠธํ•œ ์ˆซ์ž๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. */
        delay(100); 			/* 100๋ฐ€๋ฆฌ์ดˆ ๋™์•ˆ ์‰ฐ๋‹ค. */
    }

END:
    printf("Good Bye!\n");

    /* ์‚ฌ์šฉ์ด ๋๋‚œ ์žฅ์น˜๋ฅผ ์ •๋ฆฌ */
    wiringPiI2CWriteReg8(pressure_fd, CTRL_REG1, 0x00);
    close(pressure_fd);
    wiringPiI2CWriteReg8(temperature_fd, CTRL_REG1, 0x00);
    close(temperature_fd);

    pthread_mutex_destroy(&pressure_lock); 	/* ๋ฎคํ…์Šค ์‚ญ์ œ */
    pthread_mutex_destroy(&temperature_lock);

    return 0;
}

/* ํ‚ค๋ณด๋“œ ์ž…๋ ฅ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜ */
int kbhit(void)
{
    struct termios oldt, newt; 		/* ํ„ฐ๋ฏธ๋„์— ๋Œ€ํ•œ ๊ตฌ์กฐ์ฒด */
    int ch, oldf;

    tcgetattr(0, &oldt); 		/* ํ˜„์žฌ ํ„ฐ๋ฏธ๋„์— ์„ค์ •๋œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. */
    newt = oldt;
    newt.c_lflag &= ~(ICANON | ECHO); 	/* ์ •๊ทœ ๋ชจ๋“œ ์ž…๋ ฅ๊ณผ ์—์ฝ”๋ฅผ ํ•ด์ œํ•œ๋‹ค. */
    tcsetattr(0, TCSANOW, &newt); 	/* ์ƒˆ ๊ฐ’์œผ๋กœ ํ„ฐ๋ฏธ๋„์„ ์„ค์ •ํ•œ๋‹ค. */
    oldf = fcntl(0, F_GETFL, 0);
    fcntl(0, F_SETFL, oldf | O_NONBLOCK); 	/* ์ž…๋ ฅ์„ ๋…ผ๋ธ”๋กœํ‚น ๋ชจ๋“œ๋กœ ์„ค์ •ํ•œ๋‹ค. */

    ch = getchar();

    tcsetattr(0, TCSANOW, &oldt); 	/* ๊ธฐ์กด์˜ ๊ฐ’์œผ๋กœ ํ„ฐ๋ฏธ๋„์˜ ์†์„ฑ์„ ๋ฐ”๋กœ ์ ์šฉํ•œ๋‹ค. */
    fcntl(0, F_SETFL, oldf);
    if(ch != EOF) {
        ungetc(ch, stdin); 		/* ์•ž์—์„œ ์ฝ์€ ์œ„์น˜๋กœ ์ด์ „์œผ๋กœ ํฌ์ธํ„ฐ๋ฅผ ๋Œ๋ ค์ค€๋‹ค. */
        return 1;
    }

    return 0;
}

/* ๊ธฐ์••๊ณผ ์˜จ๋„ ๊ณ„์‚ฐ์„ ์œ„ํ•œ ํ•จ์ˆ˜ */
void getPressure(int fd, double *temperature, double *pressure)
{
    int result;
    unsigned char temp_out_l = 0, temp_out_h = 0; 	/* ์˜จ๋„๋ฅผ ๊ณ„์‚ฐํ•˜๊ธฐ ์œ„ํ•œ ๋ณ€์ˆ˜ */
    unsigned char press_out_xl = 0; 	/* ๊ธฐ์••์„ ๊ณ„์‚ฐํ•˜๊ธฐ ์œ„ํ•œ ๋ณ€์ˆ˜ */
    unsigned char press_out_l = 0;
    unsigned char press_out_h = 0;
    short temp_out = 0; 		/* ์˜จ๋„์™€ ์••๋ ฅ์„ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ๋ณ€์ˆ˜ */
    int press_out = 0;

    /* ์ธก์ •์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ */
    do {
        delay(25); 			/* 25๋ฐ€๋ฆฌ์ดˆ ๋Œ€๊ธฐ */
        result = wiringPiI2CReadReg8(fd, CTRL_REG2);
    } while(result != 0);

    /* ์ธก์ •๋œ ์˜จ๋„ ๊ฐ’ ์ฝ๊ธฐ(2๋ฐ”์ดํŠธ ์ฝ๊ธฐ) */
    temp_out_l = wiringPiI2CReadReg8(fd, PTEMP_OUT_L);
    temp_out_h = wiringPiI2CReadReg8(fd, PTEMP_OUT_H);

    /* ์ธก์ •๋œ ๊ธฐ์•• ๊ฐ’ ์ฝ๊ธฐ(3๋ฐ”์ดํŠธ ์ฝ๊ธฐ) */
    press_out_xl = wiringPiI2CReadReg8(fd, PRESS_OUT_XL);
    press_out_l = wiringPiI2CReadReg8(fd, PRESS_OUT_L);
    press_out_h = wiringPiI2CReadReg8(fd, PRESS_OUT_H);

    /* ๊ฐ๊ฐ ์ธก์ •ํ•œ ๊ฐ’๋“ค์„ ํ•ฉ์„ฑํ•ด์„œ ์˜จ๋„(16๋น„ํŠธ)์™€ ๊ธฐ์••(24๋น„ํŠธ) ๊ฐ’ ์ƒ์„ฑ(๋น„ํŠธ/์‹œํ”„ํŠธ ์ด์šฉ) */
    temp_out = temp_out_h << 8 | temp_out_l;
    press_out = press_out_h << 16 | press_out_l << 8 | press_out_xl;

    /* ์ถœ๋ ฅ๊ฐ’ ๊ณ„์‚ฐ */
    *temperature = 42.5 + (temp_out / 480.0);
    *pressure = press_out / 4096.0;
}

/* ์˜จ๋„์™€ ์Šต๋„๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜ */
void getTemperature(int fd, double *temperature, double *humidity)
{
    int result;

    /* ์ธก์ •์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ */
    do {
        delay(25); 			/* 25๋ฐ€๋ฆฌ์ดˆ ๋Œ€๊ธฐ */
        result = wiringPiI2CReadReg8(fd, CTRL_REG2);
    } while(result != 0);

    /* ์˜จ๋„(LSB(ADC))๋ฅผ ์œ„ํ•œ ๋ณด์ •๊ฐ’(x-๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•œ 2์ง€์ ) ์ฝ๊ธฐ */
    unsigned char t0_out_l = wiringPiI2CReadReg8(fd, T0_OUT_L);
    unsigned char t0_out_h = wiringPiI2CReadReg8(fd, T0_OUT_H);
    unsigned char t1_out_l = wiringPiI2CReadReg8(fd, T1_OUT_L);
    unsigned char t1_out_h = wiringPiI2CReadReg8(fd, T1_OUT_H);

    /* ์˜จ๋„(°C)๋ฅผ ์œ„ํ•œ ๋ณด์ •๊ฐ’(y-๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•œ 2์ง€์ ) ์ฝ๊ธฐ */
    unsigned char t0_degC_x8 = wiringPiI2CReadReg8(fd, T0_degC_x8);
    unsigned char t1_degC_x8 = wiringPiI2CReadReg8(fd, T1_degC_x8);
    unsigned char t1_t0_msb = wiringPiI2CReadReg8(fd, T1_T0_MSB);

    /* ์Šต๋„(LSB(ADC))๋ฅผ ์œ„ํ•œ ๋ณด์ •๊ฐ’(x-๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•œ 2์ง€์ ) ์ฝ๊ธฐ */
    unsigned char h0_out_l = wiringPiI2CReadReg8(fd, H0_T0_OUT_L);
    unsigned char h0_out_h = wiringPiI2CReadReg8(fd, H0_T0_OUT_H);
    unsigned char h1_out_l = wiringPiI2CReadReg8(fd, H1_T0_OUT_L);
    unsigned char h1_out_h = wiringPiI2CReadReg8(fd, H1_T0_OUT_H);

    /*์Šต๋„(% rH)๋ฅผ ์œ„ํ•œ ๋ณด์ •๊ฐ’(y-๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•œ 2์ง€์ ) ์ฝ๊ธฐ */
    unsigned char h0_rh_x2 = wiringPiI2CReadReg8(fd, H0_rH_x2);
    unsigned char h1_rh_x2 = wiringPiI2CReadReg8(fd, H1_rH_x2);

    /* ๊ฐ๊ฐ ์ธก์ •ํ•œ ๊ฐ’๋“ค์„ ํ•ฉ์„ฑํ•ด์„œ ์˜จ๋„(x-๊ฐ’) ๊ฐ’ ์ƒ์„ฑ(๋น„ํŠธ/์‹œํ”„ํŠธ ์ด์šฉ) */
    short s_t0_out = t0_out_h << 8 | t0_out_l;
    short s_t1_out = t1_out_h << 8 | t1_out_l;

    /* ๊ฐ๊ฐ ์ธก์ •ํ•œ ๊ฐ’๋“ค์„ ํ•ฉ์„ฑํ•ด์„œ ์Šต๋„(x-๊ฐ’) ๊ฐ’ ์ƒ์„ฑ(๋น„ํŠธ/์‹œํ”„ํŠธ ์ด์šฉ) */
    short s_h0_t0_out = h0_out_h << 8 | h0_out_l;
    short s_h1_t0_out = h1_out_h << 8 | h1_out_l;

    /* 16๋น„ํŠธ์™€ 10๋น„ํŠธ์˜ ๊ฐ’ ์ƒ์„ฑ(๋น„ํŠธ ๋งˆ์Šคํฌ/์‹œํ”„ํŠธ ์ด์šฉ) */
    unsigned short s_t0_degC_x8 = (t1_t0_msb & 3) << 8 | t0_degC_x8;
    unsigned short s_t1_degC_x8 = ((t1_t0_msb & 12) >> 2) << 8 | t1_degC_x8;

    /* ์˜จ๋„ ๋ณด์ •๊ฐ’(y-๊ฐ’) ๊ณ„์‚ฐ */
    double d_t0_degC = s_t0_degC_x8 / 8.0;
    double d_t1_degC = s_t1_degC_x8 / 8.0;

    /* ์Šต๋„ ๋ณด์ •๊ฐ’(y-๊ฐ’) ๊ณ„์‚ฐ */
    double h0_rH = h0_rh_x2 / 2.0;
    double h1_rH = h1_rh_x2 / 2.0;

    /* ์˜จ๋„์™€ ์Šต๋„์˜ ๊ณ„์‚ฐ์„ ์œ„ํ•œ ๋ณด์ • ์„ ํ˜• ์ง์„  ๊ทธ๋ž˜ํ”„ 'y = mx + c' ๊ณต์‹์„ ๊ณ„์‚ฐ */
    double t_gradient_m = (d_t1_degC - d_t0_degC) / (s_t1_out - s_t0_out);
    double t_intercept_c = d_t1_degC - (t_gradient_m * s_t1_out);
    double h_gradient_m = (h1_rH - h0_rH) / (s_h1_t0_out - s_h0_t0_out);
    double h_intercept_c = h1_rH - (h_gradient_m * s_h1_t0_out);

    /* ์ฃผ๋ณ€์˜ ์˜จ๋„ ์ฝ๊ธฐ(2๋ฐ”์ดํŠธ ์ฝ๊ธฐ) */
    unsigned char t_out_l = wiringPiI2CReadReg8(fd, TEMP_OUT_L);
    unsigned char t_out_h = wiringPiI2CReadReg8(fd, TEMP_OUT_H);

    /* 16๋น„ํŠธ ๊ฐ’ ์ƒ์„ฑ */
    short s_t_out = t_out_h << 8 | t_out_l;

    /* ์ฃผ๋ณ€์˜ ์Šต๋„ ์ฝ๊ธฐ(2๋ฐ”์ดํŠธ ์ฝ๊ธฐ) */
    unsigned char h_t_out_l = wiringPiI2CReadReg8(fd, H_T_OUT_L);
    unsigned char h_t_out_h = wiringPiI2CReadReg8(fd, H_T_OUT_H);

    /* 16๋น„ํŠธ ๊ฐ’ ์ƒ์„ฑ */
    short s_h_t_out = h_t_out_h << 8 | h_t_out_l;

    /* ์ฃผ๋ณ€์˜ ์˜จ๋„ ๊ณ„์‚ฐ */
    *temperature = (t_gradient_m * s_t_out) + t_intercept_c;

    /* ์ฃผ๋ณ€์˜ ์Šต๋„ ๊ณ„์‚ฐ */
    *humidity = (h_gradient_m * s_h_t_out) + h_intercept_c;
}

void* webserverFunction(void* arg)
{
    int ssock;
    pthread_t thread;
    struct sockaddr_in servaddr, cliaddr;
    unsigned int len;
    int port = (int)(arg);

    /* ์„œ๋ฒ„๋ฅผ ์œ„ํ•œ ์†Œ์ผ“์„ ์ƒ์„ฑํ•œ๋‹ค. */
    ssock = socket(AF_INET, SOCK_STREAM, 0);
    if(ssock == -1) {
        perror("socket()");
        exit(1);
    }

    /* ์ž…๋ ฅ๋ฐ›๋Š” ํฌํŠธ ๋ฒˆํ˜ธ๋ฅผ ์ด์šฉํ•ด์„œ ์„œ๋น„์Šค๋ฅผ ์šด์˜์ฒด์ œ์— ๋“ฑ๋กํ•œ๋‹ค. */
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);
    if(bind(ssock, (struct sockaddr *)&servaddr, sizeof(servaddr))==-1) {
        perror("bind()");
        exit(1);
    }
    
    /* ์ตœ๋Œ€ 10๋Œ€์˜ ํด๋ผ์ด์–ธํŠธ์˜ ๋™์‹œ ์ ‘์†์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. */
    if(listen(ssock, 10) == -1) {
        perror("listen()");
        exit(1);
    }

    while(is_run) {
        char mesg[BUFSIZ];
        int csock;

        /* ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ ๊ธฐ๋‹ค๋ฆฐ๋‹ค. */
        len = sizeof(cliaddr);
        csock = accept(ssock, (struct sockaddr*)&cliaddr, &len);

        /* ๋„คํŠธ์›Œํฌ ์ฃผ์†Œ๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ณ€๊ฒฝ */
        inet_ntop(AF_INET, &cliaddr.sin_addr, mesg, BUFSIZ);
        printf("Client IP : %s:%d\n", mesg, ntohs(cliaddr.sin_port));

        /* ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด ์Šค๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค. */
        pthread_create(&thread, NULL, clnt_connection, &csock);
    //    pthread_join(thread, NULL);
    }

    return 0;
}
    
void *clnt_connection(void *arg)
{
    /* ์Šค๋ ˆ๋“œ๋ฅผ ํ†ตํ•ด์„œ ๋„˜์–ด์˜จ arg๋ฅผ int ํ˜•์˜ ํŒŒ์ผ ๋””์Šคํฌ๋ฆฝํ„ฐ๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค. */
    int csock = *((int*)arg);
    FILE *clnt_read, *clnt_write;
    char reg_line[BUFSIZ], reg_buf[BUFSIZ];
    char method[BUFSIZ], type[BUFSIZ];
    char filename[BUFSIZ], *ret;

    /* ํŒŒ์ผ ๋””์Šคํฌ๋ฆฝํ„ฐ๋ฅผ FILE ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค. */
    clnt_read = fdopen(csock, "r");
    clnt_write = fdopen(dup(csock), "w");

    /* ํ•œ ์ค„์˜ ๋ฌธ์ž์—ด์„ ์ฝ์–ด์„œ reg_line ๋ณ€์ˆ˜์— ์ €์žฅํ•œ๋‹ค. */
    fgets(reg_line, BUFSIZ, clnt_read);
    
    /* reg_line ๋ณ€์ˆ˜์— ๋ฌธ์ž์—ด์„ ํ™”๋ฉด์— ์ถœ๋ ฅํ•œ๋‹ค. */
    fputs(reg_line, stdout);

    /* ' ' ๋ฌธ์ž๋กœ reg_line์„ ๊ตฌ๋ถ„ํ•ด์„œ ์š”์ฒญ ๋ผ์ธ์˜ ๋‚ด์šฉ(๋ฉ”์†Œ๋“œ)๋ฅผ ๋ถ„๋ฆฌํ•œ๋‹ค. */
    ret = strtok(reg_line, "/ ");
    strcpy(method, (ret != NULL)?ret:"");
    if(strcmp(method, "POST") == 0) { 		/* POST ๋ฉ”์†Œ๋“œ์ผ ๊ฒฝ์šฐ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค. */
        sendOk(clnt_write); 			/* ๋‹จ์ˆœํžˆ OK ๋ฉ”์‹œ์ง€๋ฅผ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ณด๋‚ธ๋‹ค. */
        goto END;
    } else if(strcmp(method, "GET") != 0) {	/* GET ๋ฉ”์†Œ๋“œ๊ฐ€ ์•„๋‹ ๊ฒฝ์šฐ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค. */
        sendError(clnt_write); 			/* ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ณด๋‚ธ๋‹ค. */
        goto END;
    }

    ret = strtok(NULL, " "); 			/* ์š”์ฒญ ๋ผ์ธ์—์„œ ๊ฒฝ๋กœ(path)๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. */
    strcpy(filename, (ret != NULL)?ret:"");
    if(filename[0] == '/') { 			/* ๊ฒฝ๋กœ๊ฐ€ '/'๋กœ ์‹œ์ž‘๋  ๊ฒฝ์šฐ /๋ฅผ ์ œ๊ฑฐํ•œ๋‹ค. */
        for(int i = 0, j = 0; i < BUFSIZ; i++, j++) {
            if(filename[0] == '/') j++;
            filename[i] = filename[j];
            if(filename[j] == '\0') break;
        }
    }

    /* URL์— ๊ฒฝ๋กœ(path)์ด ์—†์„ ๋•Œ ๊ธฐ๋ณธ ์ฒ˜๋ฆฌ */
    if(!strlen(filename)) strcpy(filename, "index.html");

    /* ๋ผ์ฆˆ๋ฒ ๋ฆฌ ํŒŒ์ด๋ฅผ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•œ HTML ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•ด์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค. */
    if(strstr(filename, "?") != NULL) {
        char optLine[BUFSIZ];
        char optStr[32][BUFSIZ];
        char opt[BUFSIZ], var[BUFSIZ], *tok;
        int count = 0;

        ret = strtok(filename, "?");
        if(ret == NULL) goto END;
        strcpy(filename, ret);
        ret = strtok(NULL, "?");
        if(ret == NULL) goto END;
        strcpy(optLine, ret);

        /* ์˜ต์…˜์„ ๋ถ„์„ํ•œ๋‹ค. */
        tok = strtok(optLine, "&");
        while(tok != NULL) {
            strcpy(optStr[count++], tok);
            tok = strtok(NULL, "&");
        }
 
        /* ๋ถ„์„ํ•œ ์˜ต์…˜์„ ์ฒ˜๋ฆฌํ•œ๋‹ค. */
        for(int i = 0; i < count; i++) {
            strcpy(opt, strtok(optStr[i], "="));
            strcpy(var, strtok(NULL, "="));
            printf("%s = %s\n", opt, var);
            if(!strcmp(opt, "led") && !strcmp(var, "On")) { 		/* 8×8 LED ๋งคํŠธ๋ฆญ์Šค๋ฅผ ์ผฌ */
            } else if(!strcmp(opt, "led") && !strcmp(var, "Off")) { 	/* 8×8 LED ๋งคํŠธ๋ฆญ์Šค๋ฅผ ๋” */
            }
        }
    }

    /* ๋ฉ”์‹œ์ง€ ํ—ค๋”๋ฅผ ์ฝ์–ด์„œ ํ™”๋ฉด์— ์ถœ๋ ฅํ•˜๊ณ  ๋‚˜๋จธ์ง€๋Š” ๋ฌด์‹œํ•œ๋‹ค. */
    do {
        fgets(reg_line, BUFSIZ, clnt_read);
        fputs(reg_line, stdout);
        strcpy(reg_buf, reg_line);
        char* buf = strchr(reg_buf, ':');
    } while(strncmp(reg_line, "\r\n", 2)); 	/* ์š”์ฒญ ํ—ค๋”๋Š” ‘\r\n’์œผ๋กœ ๋๋‚œ๋‹ค. */

    /* ํŒŒ์ผ์˜ ์ด๋ฆ„์„ ์ด์šฉํ•ด์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ํŒŒ์ผ์˜ ๋‚ด์šฉ์„ ๋ณด๋‚ธ๋‹ค. */
    sendData(clnt_write, type, filename);

END:
    fclose(clnt_read); 				/* ํŒŒ์ผ์˜ ์ŠคํŠธ๋ฆผ์„ ๋‹ซ๋Š”๋‹ค. */
    fclose(clnt_write);
    pthread_exit(0); 				/* ์Šค๋ ˆ๋“œ๋ฅผ ์ข…๋ฃŒ์‹œํ‚จ๋‹ค. */

    return (void*)NULL;
}
    
int sendData(FILE* fp, char *ct, char *filename)
{
    /* ํด๋ผ์ด์–ธํŠธ๋กœ ๋ณด๋‚ผ ์„ฑ๊ณต์— ๋Œ€ํ•œ ์‘๋‹ต ๋ฉ”์‹œ์ง€ */
    char protocol[ ] = "HTTP/1.1 200 OK\r\n";
    char server[ ] = "Server:Netscape-Enterprise/6.0\r\n";
    char cnt_type[ ] = "Content-Type:text/html\r\n";
    char end[ ] = "\r\n"; 			/* ์‘๋‹ต ํ—ค๋”์˜ ๋์€ ํ•ญ์ƒ \r\n */
    char html[BUFSIZ];
    double temperature, humidity;
    double t_c = 0.0; 				/* ์˜จ๋„์™€ ์••๋ ฅ์„ ์ถœ๋ ฅํ•˜๊ธฐ ์œ„ํ•œ ๋ณ€์ˆ˜ */
    double pressure = 0.0;

    /* LPS25H ์žฅ์น˜ ์ดˆ๊ธฐํ™” */
    wiringPiI2CWriteReg8(pressure_fd, CTRL_REG1, 0x00);
    wiringPiI2CWriteReg8(pressure_fd, CTRL_REG1, 0x84);
    wiringPiI2CWriteReg8(pressure_fd, CTRL_REG2, 0x01);
    getPressure(pressure_fd, &t_c, &pressure); 	/* ๊ธฐ์••/์˜จ๋„๋ฅผ ์œ„ํ•œ ํ•จ์ˆ˜ */

    /* HTS221 ์žฅ์น˜ ์ดˆ๊ธฐํ™” */
    wiringPiI2CWriteReg8(temperature_fd, CTRL_REG1, 0x00);
    wiringPiI2CWriteReg8(temperature_fd, CTRL_REG1, 0x84);
    wiringPiI2CWriteReg8(temperature_fd, CTRL_REG2, 0x01);
    getTemperature(temperature_fd, &temperature, &humidity); 	/* ์˜จ๋„/์Šต๋„๋ฅผ ์œ„ํ•œ ํ•จ์ˆ˜ */

    sprintf(html, "<html><head><meta http-equiv=\"Content-Type\" " \
                  "content=\"text/html; charset=UTF-8\" />" \
                  "<title>Raspberry Pi Controller</title></head><body><table>" \
                  "<tr><td>Temperature</td><td colspan=2>" \
                  "<input readonly name=\"temperature\"value=%.3f></td></tr>" \
                  "<tr><td>Humidity</td><td colspan=2>" \
                  "<input readonly name=\"humidity\"value=%.3f></td></tr>" \
                  "<tr><td>Pressure</td><td colspan=2>" \
                  "<input readonly name=\"pressure\"value=%.3f></td></tr></table>" \
                  "<form action=\"index.html\" method=\"GET\" "\
                  "onSubmit=\"document.reload()\"><table>" \
                  "<tr><td>8x8 LED Matrix</td><td>" \
                  "<input type=radio name=\"led\" value=\"On\" checked=checked>On</td>" \
                  "<td><input type=radio name=\"led\" value=\"Off\">Off</td>" \
                  "</tr><tr><td>Submit</td>" \
                  "<td colspan=2><input type=submit value=\"Submit\"></td></tr>" \
                  "</table></form></body></html>",
                  temperature, humidity, pressure);

    fputs(protocol, fp);
    fputs(server, fp);
    fputs(cnt_type, fp);
    fputs(end, fp);
    fputs(html, fp);
    fflush(fp);

    return 0;
}
    
void sendOk(FILE* fp)
{
    /* ํด๋ผ์ด์–ธํŠธ์— ๋ณด๋‚ผ ์„ฑ๊ณต์— ๋Œ€ํ•œ HTTP ์‘๋‹ต ๋ฉ”์‹œ์ง€ */
    char protocol[ ] = "HTTP/1.1 200 OK\r\n";
    char server[ ] = "Server: Netscape-Enterprise/6.0\r\n\r\n";

    fputs(protocol, fp);
    fputs(server, fp);
    fflush(fp);
}
    
void sendError(FILE* fp)
{
    /* ํด๋ผ์ด์–ธํŠธ๋กœ ๋ณด๋‚ผ ์‹คํŒจ์— ๋Œ€ํ•œ HTTP ์‘๋‹ต ๋ฉ”์‹œ์ง€ */
    char protocol[ ] = "HTTP/1.1 400 Bad Request\r\n";
    char server[ ] = "Server: Netscape-Enterprise/6.0\r\n";
    char cnt_len[ ] = "Content-Length:1024\r\n";
    char cnt_type[ ] = "Content-Type:text/html\r\n\r\n";

    /* ํ™”๋ฉด์— ํ‘œ์‹œ๋  HTML์˜ ๋‚ด์šฉ */
    char content1[ ] = "<html><head><title>BAD Connection</title></head>";
    char content2[ ] = "<body><font size=+5>Bad Request</font></body></html>";
    printf("send_error\n");

    fputs(protocol, fp);
    fputs(server, fp);
    fputs(cnt_len, fp);
    fputs(cnt_type, fp);
    fputs(content1, fp);
    fputs(content2, fp);
    fflush(fp);
}

/*
HTML ๋ฌธ์„œ ์•ˆ์— ์˜จ๋„์™€ ์Šต๋„ ๋“ฑ์„ ์ง์ ‘ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ๋„๋ก ์‚ฝ์ž…ํ•จ + lED ์ผœ๊ณ  ๋Œ ์ˆ˜ ์žˆ๋Š” ๋ผ๋””์˜ค ๋ฒ„ํŠผ + ์Œ์•…์„ ์—ฐ์ฃผํ•˜๊ณ  ๋ชจํ„ฐ๋ฅผ ์ œ์–ด
์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ์›น ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ผ ๋•Œ <FORM> ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค
<FORM> ํƒœ๊ทธ๋Š” POST ๋ฉ”์‹œ์ง€๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด์ง€๋งŒ, ์•ž ์›น ์„œ๋ฒ„๋Š” GET ๋ฉ”์†Œ๋“œ๋งŒ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์—
<FORM> ํƒœ๊ทธ์˜ method ์†์„ฑ์„ ์ด์šฉํ•˜์—ฌ GET ๋ฉ”์†Œ๋“œ๋กœ ๋ณด๋‚ด๋„๋ก ์„ค์ •ํ•œ๋‹ค

์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ์›น ํŽ˜์ด์ง€๋ฅผ ๋ถˆ๋Ÿฌ๋“ค์—ฌ์„œ ์˜ต์…˜์„ ์„ ํƒํ•˜๊ณ  Submit ๋ฒ„ํŠผ์„ ์„ ํƒํ•˜๋ฉด, ์›น ์„œ๋ฒ„๋กœ 
'http://์„œ๋ฒ„์ฃผ์†Œ:ํฌํŠธ๋ฒˆํ˜ธ/index.html?led=Off' ์š”์ฒญ์˜ GET ๋ฉ”์†Œ๋“œ๋กœ ์ „๋‹ฌ๋œ๋‹ค
์›น ์„œ๋ฒ„์—์„œ๋Š” GET ๋ฉ”์†Œ๋“œ๋กœ ์ „์†ก๋œ ๋ฉ”์‹œ์ง€๋ฅผ ๋ถ„์„ํ•˜๊ณ  ๋ผ์ฆˆ๋ฒ ๋ฆฌ ํŒŒ์ด์˜ GPIO ์— ํ•ด๋‹น ๋ช…๋ น์„ ๋‚ด๋ฆด ์ˆ˜ ์žˆ๋‹ค

์ฝ”๋“œ๋ฅผ ๋นŒ๋“œ ์‹œ Pthread ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, wiringPi ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ชจ๋‘ ๋งํฌํ•ด์•ผ ํ•œ๋‹ค
์“ฐ๋‹ˆ๋Š” ์˜จ์Šต๋„ ์ธก์ • ์„ผ์„œ๊ฐ€ ์—†์–ด์„œ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค ใ… 

gani@gani:~/raspi/NetworkProgramming $ gcc -o rpi6 rpi6.c -lpthread -lwiringPi
gani@gani:~/raspi/NetworkProgramming $ sudo ./rpi6 8080
*/
gcc -o rpi6 rpi6.c -lpthread -lwiringPi
sudo ./rpi6 8080

 

728x90
๋ฐ˜์‘ํ˜•
Comments