PC 프로그램 내부의 화면 표시 스케일을 수정해 달라는 요청이 왔다.

기존엔 1152.00으로 표시되는 걸 11.52로 표시해달라는 것...

 

원시 데이터를 가공하는 부분을 다음과 같이 수정했다.

 

double volB = Convert.ToInt32(hexStrVolB, 16) / 100;

 

이랬더니 문제가...

 

소숫점 자리수는 앞으로 두자리 땡겨지는데 마지막 두 자리를 버림 처리 해버리는 것이였다.

 

찾아보니 

 

https://bumday.tistory.com/193

 

[C#] int형 변수끼리 나눗셈 결과 소숫점 이하 값이 버려지는 현상

JavaScript과 같은 동적 타이핑(dynamic typing)을 지원하는 언어를 사용하다가 C#으로 넘어와 개발하다보면 자료형에 대해 간과하다가 이슈가 생길 때가 있다 . 이번 포스팅에서는 int형 변수끼리 나눗

bumday.tistory.com

 

아...

C#에서의 나눗셈은 정수 나누기 정수를 하면

결과가 실수더라도 실수가 저장되지 않고 소숫점 이하값이 버려지고 

정수부만 변수에 들어간다는 것...

 

double volB = (double)Convert.ToInt32(hexStrVolB, 16) / 100; 나

 

double volB = Convert.ToInt32(hexStrVolB, 16) / 100.0;

 

처럼 한 쪽이 실수형이여야한다는 것.

 

지식이 늘었다.

'Tech > C#' 카테고리의 다른 글

C# Modbus 프로그램 간혈적 통신 중단 해결기  (0) 2024.07.02

 

얼마 전 싼맛에 XMP 풀뱅크를 한답시고 듀얼킷 두개를 야심차게 산 적이 있었다.

 

팀그룹 티포스 CL18 3600 메모리로 기억하는데, 

 

두개 두개 나눠 끼면 XMP 수치가 잘 들어가고

 

풀뱅을 했다간 거의 모든 램타이밍과 클럭에서 부팅 실패 / 게임 크래시 / PC 멈춤 등 불안한 모습이 보였다.

 

QVL 리스트에도 있었는데 왜 이러지 미친듯이 찾아본 결과 그냥 태생이 그런 거란다.

 

깡수율이 좋은 램이면 풀뱅해도 XMP를 먹거나 전압 조정 램타 조정 통해서 먹는 경우도 있지만

 

포텐셜이 낮은 메모리의 경우 쉽지 않다고...

 

바로 수업료 지불하고 반품 했다.

 

사용자와 장비 사이에 껴서 원격으로 장비의 데이터를 확인할 수 있게 해주는 HMI라는 녀석은

장비가 없으면 깡통에 불과하다.

 

그 깡통인 HMI를 유지보수하는 게 주 업무 중 하나인데, 

불특정 시간마다 되면 PC 프로그램과 장비의 연결이 끊긴다는 내용을 전달 받았다.

 

 

일반적인 인터넷 환경과 달리

이런 전기 산업 쪽 장비는 HMI가 클라이언트가 되어 서버 역활을 하는 장비에게 데이터를 요청하고, 

요청을 받은 장비는 내부 메모리 데이터를 긁어다가 패킷을 보내준다.

HMI 프로그램은 받은 데이터를 해석하고, 맵핑하여 사용자에게 전시하는 역활을 하고,

사용자 조작을 반대로 장비로 전송하기도 한다.

 

하지만 그 과정에서 누군가가 연결을 끊어버려 그 절차가 진행되지 않는 것.

 

처음에는 일반적인 인터넷 환경을 상정하고 PC SW가 서버, 장비가 클라이언트라 생각했으나

PC 측의 연결 함수를 아무리 뜯어봐도 서버 역활을 하는 것 같지는 않았다.

소캣 연결 절차를 따라가봐도 내가 상대방에게 요청하는 것 밖에 없네?

 

...내 생각이 잘못된걸 그때서야 알았다. 아 이거 완전 반대구나.

 

내 환경에서는 죽어도 재현이 안됐기에 고민 끝에 다음과 같은 방식으로 증상을 재현하였다.

 

1. 우선 PC와 연결할 때는 장비 전원을 켜둔다. (소켓 열림)

 

2. 데이터를 한번 요청하여 화면에 제대로 표시되는 지 확인한다.

 

3. 장비 전원을 껐다가 다시 켠 후 데이터를 요청하여 전달받은 문제가 발생한지 확인한다.(소켓 닫힘)

 

 

실제 원인은 이랬다.

 

모종의 이유로 장비 측에서 PC 프로그램과의 소켓을 끊어버린 것.

 

PC 프로그램은 별도의 소켓 Life Check를 진행하지 않고 있기 때문에 

데이터 값이 문제 있다는 오류 메세지만 출력되고 있었다.

 

그 상황에서 연결 설정을 통해 다시 연결하고 데이터를 요청하면 정상적으로 받아왔다.

 

그렇다고 나는 FW 개발자가 아니니 장비 소스코드를 뜯어볼 수도 없는 노릇.

 

그래서 PC 프로그램의 연결 소스코드를 수정하여 해결하기로 했다.

 

C#의 Socket 클래스를 살펴본 봐 Poll과 Available 이라는 기능이 있는 걸 알 수 있었다.

Poll은 인자로 최대 시간(MS)와 Mode를 받아 결과를 반환한다.

  • SelectMode.SelectRead: 소켓이 읽기 가능한 상태인지 확인.
  • SelectMode.SelectWrite: 소켓이 쓰기 가능한 상태인지 확인.
  • SelectMode.SelectError: 소켓에 오류가 있는지 확인.

Available 은 소켓에 수신 대기중인 바이트가 얼마나 있는지 감지하는 기능이다.

 

해당 함수를 활용하면 소켓 연결을 확인할 수 있을 것으로 생각 되어 코드를 아래와 같이 작성하고 적용하였다.

C#-소켓통신간-클라이언트-끊어짐-감지

 

우선 통신 연결이 처음 성공하면 모니터링 함수를 통해 1초 간격으로 소켓 상태를 확인하고

수신 대기중인 바이트를 계산하여 0바이트인지 확인한다.

 

 

Poll과 Available의 결과가 true로 반환되어 소켓 닫힘이 감지되면 아래의 재 연결 함수로 이동하게 된다.

 

 

해당 함수는 소켓 연결을 재 시도하고, 오류가 발생하면 5초 뒤에 다시 시도하는 역활을 하게 된다.

 

 

해당 작업된 결과물 송부해서 지금까지도 기다려본 바,

 

다행이 별 다른 피드백은 없는 것으로 보아하니...

고안했던 방법이 잘 먹힌 거 같다.

 

2주~3주 넘게 고민했는데 쉽게 풀려서 정말 다행였다랄까.

 

 

'Tech > C#' 카테고리의 다른 글

C#의 정수형 나눗셈시 주의사항.  (1) 2025.02.21

실습 환경 : mariadb, 우분투 리눅스, 아파치 웹 서버, PHP 7.4

실습 보드 : ESP8266 개발 보드 + DHT22 센서

 

설정 간 문제 해결 과정

mariadb 설치 후 sudo mysql -u root 실행 시 Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2) 오류 발생

 

0. 서비스 재실행

sudo service mysql restart 또는 sudo systemctl restart mariadb 입력하여 서비스 재실행

 

1. 서비스 재실행 자체가 실패할 경우 (+삭제 후 실행시 동일증상 발생해도 마찬가지)

/var/lib/mysql 폴더 강제 삭제 후 데이터 재생성 해주기,

cd var/lib/ 입력하여 경로 이동,

sudo rm -r mysql mysql 폴더 강제 삭제(* rm 명령어로 폴더 삭제할 경우 -r 옵션을 rm 명령어 뒤에 붙힌다)

sudo mysql_install_db --user=mysql 

sudo service mysql start 또는 sudo systemctl start mariadb 입력하여 서비스 정상 동작 확인하기

 

 

GET 방식으로 웹서버 전송

받는 측 PHP 파일에서 별도의 수신 확인 응답을 보내지 않기 때문에

시리얼 모니터 상으론 에러코드 -11번이 출력되나 정상적으로 DB에 값이 저장되는 것으로 보임.

//GET 방식으로 웹서버로 전송.

#include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include "DHT.h"
#define DHTPIN D2     // Digital pin connected to the DHT sensor
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
DHT dht(DHTPIN, DHTTYPE);
float h;
float t;
const char* ssid = "";
const char* password = "";

//Your Domain name with URL path or IP address with path
String serverName = "<http://192.168.2.175/test2.php>";

// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long lastTime = 0;
// Timer set to 10 minutes (600000)
//unsigned long timerDelay = 600000;
// Set timer to 5 seconds (5000)
unsigned long timerDelay = 5000;

void setup() {
  Serial.begin(115200); 
  dht.begin();
   WiFi.begin(ssid, password);
 
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
   Serial.println("Connecting to WiFi..");
  }
 
  Serial.println("Connected to the WiFi network");
}

void loop() {
  readtemphum();
  
  
  //Send an HTTP POST request every 10 minutes
  if ((millis() - lastTime) > timerDelay) {
    //Check WiFi connection status
    if(WiFi.status()== WL_CONNECTED){
      HTTPClient http;

      String serverPath = serverName + "?temp="+String(t)+"&hum="+String(h);
      
      // Your Domain name with URL path or IP address with path
      http.begin(serverPath.c_str());
      
      // Send HTTP GET request
      int httpResponseCode = http.GET();
      
      if (httpResponseCode>0) {
        Serial.print("HTTP Response code: ");
        Serial.println(httpResponseCode);
        String payload = http.getString();
        Serial.println(payload);
      }
      else {
        Serial.print("Error code: ");
        Serial.println(httpResponseCode);
      }
      // Free resources
      http.end();
    }
    else {
      Serial.println("WiFi Disconnected");
    }
    lastTime = millis();
  }
}

void readtemphum(){
    delay(2000);
   h = dht.readHumidity();
  t = dht.readTemperature();
}
  • POST 방식으로 전송하는 아두이노 코드
#include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include "DHT.h"
#define DHTPIN D2     // Digital pin connected to the DHT sensor
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
DHT dht(DHTPIN, DHTTYPE);
float h;
float t;
const char* ssid = "";
const char* password = "";
const char*  serverName = "<http://192.168.2.175/posttest.php>";
String url = "/post/";
unsigned long lastTime = 0;
unsigned long timerDelay = 5000;

void setup() {
  Serial.begin(115200);
  dht.begin();
  WiFi.begin(ssid, password);
  Serial.println("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to WiFi network with IP Address: ");
  Serial.println(WiFi.localIP());

  Serial.println("Timer set to 5 seconds (timerDelay variable), it will take 5 seconds before publishing the first reading.");
}

void loop() {
  readtemphum();
  //Send an HTTP POST request every 10 minutes
  if ((millis() - lastTime) > timerDelay) {
    //Check WiFi connection status
    if (WiFi.status() == WL_CONNECTED) {
      WiFiClient client;
      HTTPClient http;
      // Specify content-type header
      http.addHeader("Content-Type", "application/x-www-form-urlencoded");
      String postData = "temp=" + String(t) + "&hum=" + String(h);
      String address = serverName + url;
      http.begin(address);
      auto httpCode = http.POST(postData);
      Serial.println("전송 데이터 :: " + postData);
      Serial.println(httpCode); //Print HTTP return code
      String payload = http.getString();
      Serial.println(payload); //Print request response payload
      // Free resources
      http.end();
    }
    else {
      Serial.println("WiFi Disconnected");
    }
    lastTime = millis();
  }
}

void readtemphum() {
  delay(2000);
  h = dht.readHumidity();
  t = dht.readTemperature();
}
  • 네트워크 연결 후 5초 뒤 전송
  • 성공한 경우 HTTP 코드 200번 반환 (성공) 연결에 문제 있을 경우 500번 반환(실패)

WEB SERVER 측 코드

https://wikidocs.net/116936 (PHP 3분 핵심 요약집 참고하여 실습)

<?php
function db_get_pdo()
{
    $host = 'localhost';
    $port = '3306';
    $dbname = 'sensor';
    $charset = 'utf8mb4';
    $username = 'ggk_test';
    $db_pw = "";
    $dsn = "mysql:host=$host;port=$port;dbname=$dbname;charset=$charset";
    $pdo = new PDO($dsn, $username, $db_pw);
    return $pdo;
}

function db_select($query, $param=array()){
    $pdo = db_get_pdo();
    try {
        $st = $pdo->prepare($query);
        $st->execute($param);
        $result =$st->fetchAll(PDO::FETCH_ASSOC);
        $pdo = null;
        return $result;
    } catch (PDOException $ex) {
        return false;
    } finally {
        $pdo = null;
    }
}

function db_insert($query, $param = array())
{
    $pdo = db_get_pdo();
    try {
        $st = $pdo->prepare($query);
        $result = $st->execute($param);
        $last_id = $pdo->lastInsertId();
        $pdo = null;
        if ($result) {
            return $last_id;
        } else {
            return false;
        }
    } catch (PDOException $ex) {
        return false;
    } finally {
        $pdo = null;
    }
}

function db_update_delete($query, $param = array())
{
    $pdo = db_get_pdo();
    try {
        $st = $pdo->prepare($query);
        $result = $st->execute($param);
        $pdo = null;
        return $result;
    } catch (PDOException $ex) {
        return false;
    } finally {
        $pdo = null;
    }
}
?>
  • 범용적인 활용을 위해 PDO 방식 사용.
<?php
require_once("inc/db_pdo.php");

$humidity = isset($_POST['hum']) ? $_POST['hum'] : null;
$temperature = isset($_POST['temp']) ? $_POST['temp'] : null;
//$login_name = isset($_POST['login_name']) ? $_POST['login_name'] : null;

// 데이터 저장
db_insert("insert into save (hum, temp) values (:humidity, :temperature)",
    array(
        'humidity' => $humidity,
        'temperature' => $temperature,
    )
);

'Tech' 카테고리의 다른 글

Rocky Linux/Oracle Linux ) Semanage 사용하기  (0) 2022.03.16

Selinux 활성화 상태에선 포트변경이 막혀있기 때문에 (그렇다고 selinux를 꺼버리는 건 보안상 좋지 않으니)

Semanage를 사용해서 포트정보를 확인하고, 정책을 변경해야 한다.

 

리눅스 버전에 따라 semanage 사용 시 command not found 메시지가 뜨는 경우가 있는데,

yum whatprovides semanage 를 입력하여 찾을 수 있다.

명령어, 혹은 패키지명으로 확인할 수 있으며, 설명까지 확인할 수 있다.

여기서 최신버전 패키지명을 복사한 후

Sudo yum install 패키지명 으로 설치하면 완료.

'Tech' 카테고리의 다른 글

아두이노 호환보드와 웹서버간 GET, POST 방식 실습하기  (0) 2022.12.20

+ Recent posts