Files
HECO2/program/User/My_Homenet.c
T
jeon a502322188 chore: HERV 통합 저장소 재초기화 커밋
손상된 .git 히스토리(missing tree)로 재초기화 후 작업트리 전체 커밋.
.claude/ 만 제외(로컬 에이전트 설정). 구 저장소 백업(.git_corrupt_backup/) 포함.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 09:32:17 +09:00

452 lines
19 KiB
C

/* =============================================================================
* My_Homenet.c — HOMENET(UART1, 115200 N81) <-> ErvDashboard 바이너리 프로토콜
* 규격 : TestProgram/PC_ERV_Protocol.md
* (My_Uart.c 에서 분리 — PC 대시보드 통신 전용. 공용 전역/CRC16은 My_bunbaeggi.c 정의, My_define.h extern)
* ===========================================================================*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "Nano100Series.h"
#include "adc.h"
#include "gpio.h"
#include "pwm.h"
#include "timer.h"
#include "uart.h"
#include "sys.h"
#include "clk.h"
#include "My_define.h"
/* ============================================================================
* HOMENET (UART1, 115200 N81) <-> ErvDashboard 바이너리 프로토콜 (Rev2.0)
* 규격 : TestProgram/PC_ERV_Protocol.md
* 프레임 : STX(0xAA) | CMD | DATA[240] | CRC_L | CRC_H (항상 244B 고정)
* CRC-16/MODBUS (CMD~DATA[240]=241B), 리틀엔디안. 멀티바이트 값은 빅엔디안.
* PC->ERV : 명령 인자는 DATA 앞쪽, 나머지 0 패딩
* ERV->PC : STATUS(0x81) DATA=240B 상태 / ACK(0x82) DATA[0]=echoCmd DATA[1]=result
* ==========================================================================*/
#define HN_STX 0xAA
#define HN_STATUS 0x81
#define HN_ACK 0x82
#define HN_DATA_LEN 240 /* 고정 DATA 크기 */
#define HN_STATUS_LEN 240 /* 238 + 2((꺼짐)예약 잔여초 u16) */
/* ---- 수신 파서 상태 (ISR 컨텍스트) ---- Hn_rx[0]=CMD, [1..240]=DATA ---- */
static uint8_t Hn_step = 0, Hn_pos = 0, Hn_crc_lo = 0;
static uint8_t Hn_rx[1 + HN_DATA_LEN];
/* ---- 처리 대기 명령 (메인 컨텍스트로 전달) ---- */
volatile uint8_t Hn_cmd_ready = 0;
static uint8_t Hn_cmd = 0, Hn_cmd_pl[HN_DATA_LEN];
/* PC대시보드 제어 → 룸컨(RJ2) 상태 푸시 래치 (My_RJ2.c roomcon_parsing 에서 소비/클리어) */
volatile uint8_t Homenet_RJ_Request = 0;
uint8_t Homenet_Reset_State = 0;
void Homenet_Rx_Byte(uint8_t b)
{
switch(Hn_step)
{
case 0: if(b == HN_STX) Hn_step = 1; break;
case 1: Hn_rx[0] = b; Hn_pos = 0; Hn_step = 2; break; /* CMD */
case 2: /* DATA[240] */
Hn_rx[1 + Hn_pos] = b;
if(++Hn_pos >= HN_DATA_LEN) Hn_step = 3;
break;
case 3: Hn_crc_lo = b; Hn_step = 4; break; /* 첫 CRC 바이트(표준 하위) */
case 4:
{
/* 수신 바이트(하위먼저) -> CRC16() 반환형식(바이트스왑)으로 재구성하여 비교 */
uint16_t rxcrc = ((uint16_t)Hn_crc_lo << 8) | b;
uint16_t i;
/* CRC 대상 = CMD + DATA[240] = Hn_rx[0..240] (241B) */
if((CRC16(Hn_rx, (uint16_t)(1 + HN_DATA_LEN)) == rxcrc) && (Hn_cmd_ready == 0))
{
Hn_cmd = Hn_rx[0];
for(i = 0; i < HN_DATA_LEN; i++) Hn_cmd_pl[i] = Hn_rx[1 + i];
Hn_cmd_ready = 1;
}
Hn_step = 0;
break;
}
default: Hn_step = 0; break;
}
}
/* 고정 244B 송신 : STX | CMD | DATA[240] | CRC_L | CRC_H. payload는 len만큼 채우고 나머지 0 패딩 */
static void Homenet_Send_Frame(uint8_t cmd, uint8_t *payload, uint16_t len)
{
static uint8_t buf[1 + 1 + HN_DATA_LEN + 2]; /* 244 */
uint16_t crc; uint16_t n = 0; uint16_t i;
buf[n++] = HN_STX;
buf[n++] = cmd;
for(i = 0; i < HN_DATA_LEN; i++) buf[n++] = (i < len) ? payload[i] : 0;
crc = CRC16(&buf[1], (uint16_t)(1 + HN_DATA_LEN)); /* CMD+DATA[240]=241B. CRC16()는 표준 MODBUS값의 바이트스왑 반환 */
buf[n++] = (uint8_t)(crc >> 8); /* CRC_L (표준 하위바이트) - bunbaegi 와 동일 순서 */
buf[n++] = (uint8_t)(crc & 0xFF); /* CRC_H (표준 상위바이트) */
HOMENET_485_DIR = 1;
UART_Write(UART1, buf, n);
while(!(UART1->FSR & UART_FSR_TX_EMPTY_F_Msk));
while(!(UART1->FSR & UART_FSR_TE_F_Msk));
HOMENET_485_DIR = 0;
}
static void hn_w16(uint8_t *p, uint16_t v){ p[0] = (uint8_t)(v >> 8); p[1] = (uint8_t)(v & 0xFF); }
static uint8_t hn_runmode_code(void)
{
if(Power_On != 1) return 0x00;
switch(Run_Mode)
{
case MODE_VENTILATION: return 0x01;
case MODE_AUTO: return 0x02;
case MODE_AIRCLEAN: return 0x03;
case MODE_BYPASS: return 0x04;
default: return 0x01;
}
}
/* Room_Level(0좋음~4매우나쁨) -> 프로토콜 공기질코드(1매우나쁨~4좋음) */
static uint8_t hn_airq_code(uint8_t level)
{
switch(level){ case 0: return 4; case 1: return 3; case 2: return 2; default: return 1; }
}
void Homenet_Build_Status(uint8_t *p)
{
uint8_t r;
uint16_t i;
for(i = 0; i < HN_STATUS_LEN; i++) p[i] = 0;
/* --- 글로벌 0~16 --- */
p[0] = Power_On;
p[1] = hn_runmode_code();
p[2] = Auto_Concentrate; /* 0 분산 / 1 집중 */
p[3] = Fan_Mode;
p[4] = (uint8_t)(((Ext_Run_Mode == 4) ? 0x01 : 0) /* 스마트수면 */
| (Hood_YeunDong_Enable ? 0x02 : 0) /* 쾌적조리 */
| ((Ext_Run_Mode == 1) ? 0x04 : 0)); /* 안심회복 */
/* bit0 연동Enable / bit1 연동운전중 / bit2 후드 통신연결(폴 응답 생존) */
p[5] = (uint8_t)((Hood_YeunDong_Enable ? 0x01 : 0) | ((Hood_Status != 0) ? 0x02 : 0) | ((Hood_Conn_Timeout != 0) ? 0x04 : 0));
p[6] = Hyst_Preset;
hn_w16(&p[7], Pm25_Db[Hyst_Preset]);
hn_w16(&p[9], Pm10_Db[Hyst_Preset]);
hn_w16(&p[11], Voc_Db[Hyst_Preset]);
hn_w16(&p[13], Co2_Db[Hyst_Preset]);
hn_w16(&p[15], (uint16_t)Err_Code);
/* --- 각실 17~ (14B x 4) : 거실=1, 침1=2, 침2=3, 침3=4 --- */
for(r = 0; r < 4; r++)
{
uint8_t room = (uint8_t)(r + 1);
uint8_t o = (uint8_t)(17 + r * 14);
/* damper 비트맵 : bit0=급기(SA) 열림 / bit1=배기(RA/EA) 열림 */
p[o+0] = (uint8_t)((Diffuser_Dmp_Ang_SA[room] != 0 ? 0x01 : 0)
| (Diffuser_Dmp_Ang_RA[room] != 0 ? 0x02 : 0));
hn_w16(&p[o+1], SEN66_pm2p5[room]);
hn_w16(&p[o+3], SEN66_pm10p0[room]);
hn_w16(&p[o+5], (uint16_t)SEN66_VOC_value[room]);
hn_w16(&p[o+7], SEN66_CO2_value[room]);
p[o+9] = hn_airq_code(Room_Level[room]);
p[o+10] = Light_Bright[room];
hn_w16(&p[o+11], (uint16_t)Room_Level[room]); /* 각실 부하점수 = 실 Level */
p[o+13] = Fan_Mode; /* 최종 풍량(전체 단수) */
}
/* --- ERV 리셋 73 --- */
p[73] = Homenet_Reset_State;
/* --- VSP 74~109 : 환기1~4, 바이패스, 공청1~4 (각 SA,EA u16 BE) --- */
hn_w16(&p[74], s_FAN1_VEN_1_DAN); hn_w16(&p[76], s_FAN2_VEN_1_DAN);
hn_w16(&p[78], s_FAN1_VEN_2_DAN); hn_w16(&p[80], s_FAN2_VEN_2_DAN);
hn_w16(&p[82], s_FAN1_VEN_3_DAN); hn_w16(&p[84], s_FAN2_VEN_3_DAN);
hn_w16(&p[86], s_FAN1_VEN_4_DAN); hn_w16(&p[88], s_FAN2_VEN_4_DAN);
hn_w16(&p[90], s_FAN1_BYPASS_1_DAN); hn_w16(&p[92], s_FAN2_BYPASS_1_DAN);
hn_w16(&p[94], s_FAN1_AIR_1_DAN); hn_w16(&p[96], s_FAN2_AIR_1_DAN);
hn_w16(&p[98], s_FAN1_AIR_2_DAN); hn_w16(&p[100], s_FAN2_AIR_2_DAN);
hn_w16(&p[102], s_FAN1_AIR_3_DAN); hn_w16(&p[104], s_FAN2_AIR_3_DAN);
hn_w16(&p[106], s_FAN1_AIR_4_DAN); hn_w16(&p[108], s_FAN2_AIR_4_DAN);
/* --- 히스테리시스 데드밴드 테이블 110~133 : ECO/NORMAL/TURBO x (PM2.5,PM10,VOC,CO2) --- */
for(r = 0; r < 3; r++)
{
uint8_t o = (uint8_t)(110 + r * 8);
hn_w16(&p[o+0], Pm25_Db[r]);
hn_w16(&p[o+2], Pm10_Db[r]);
hn_w16(&p[o+4], Voc_Db[r]);
hn_w16(&p[o+6], Co2_Db[r]);
}
/* --- 모드별 오염단계 임계표 134~229 : 3프리셋 x [CO2,PM2.5,PM10,VOC] x L1~L4 --- */
for(r = 0; r < 3; r++)
{
int o = 134 + r * 32;
uint8_t k;
for(k = 0; k < 4; k++) hn_w16(&p[o + 0 + k*2], Co2_Thr[r][k]);
for(k = 0; k < 4; k++) hn_w16(&p[o + 8 + k*2], Pm25_Thr[r][k]);
for(k = 0; k < 4; k++) hn_w16(&p[o + 16 + k*2], Pm10_Thr[r][k]);
for(k = 0; k < 4; k++) hn_w16(&p[o + 24 + k*2], Voc_Thr[r][k]);
}
/* --- 각실 온도/습도 230~237 : 4실 x [Temp, Humi] (디퓨저 SEN66 값, 0~255 클램프) --- */
for(r = 0; r < 4; r++)
{
uint8_t room = (uint8_t)(r + 1);
uint8_t o = (uint8_t)(230 + r * 2);
int16_t t = SEN66_temperature_value[room];
int16_t h = SEN66_humidity_value[room];
p[o+0] = (uint8_t)((t < 0) ? 0 : (t > 255) ? 255 : t);
p[o+1] = (uint8_t)((h < 0) ? 0 : (h > 255) ? 255 : h);
}
/* --- (꺼짐)예약 잔여초 238~239 (u16 BE) --- */
hn_w16(&p[238], Reserve_Remain_Sec);
}
static uint8_t hn_set_vsp(uint8_t grp, uint8_t idx, uint8_t sa, uint8_t ea)
{
if(grp == 0) /* 환기 idx 1~4 */
{
switch(idx){
case 1: s_FAN1_VEN_1_DAN=sa; s_FAN2_VEN_1_DAN=ea; break;
case 2: s_FAN1_VEN_2_DAN=sa; s_FAN2_VEN_2_DAN=ea; break;
case 3: s_FAN1_VEN_3_DAN=sa; s_FAN2_VEN_3_DAN=ea; break;
case 4: s_FAN1_VEN_4_DAN=sa; s_FAN2_VEN_4_DAN=ea; break;
default: return 1;
}
}
else if(grp == 1) /* 바이패스 idx 1 */
{
if(idx == 1){ s_FAN1_BYPASS_1_DAN=sa; s_FAN2_BYPASS_1_DAN=ea; } else return 1;
}
else if(grp == 2) /* 공청 idx 1~4 */
{
switch(idx){
case 1: s_FAN1_AIR_1_DAN=sa; s_FAN2_AIR_1_DAN=ea; break;
case 2: s_FAN1_AIR_2_DAN=sa; s_FAN2_AIR_2_DAN=ea; break;
case 3: s_FAN1_AIR_3_DAN=sa; s_FAN2_AIR_3_DAN=ea; break;
case 4: s_FAN1_AIR_4_DAN=sa; s_FAN2_AIR_4_DAN=ea; break;
default: return 1;
}
}
else return 1;
return 0;
}
/* 수신 제어명령 적용 + ACK. result 0=OK / 1=ERR */
static void hn_apply_cmd(uint8_t cmd, uint8_t *pl, uint8_t len)
{
uint8_t result = 0, i;
uint8_t ack[2];
switch(cmd)
{
case 0x01: /* CTRL_POWER [onoff] */
if(len >= 1)
{
Power_On = pl[0] ? 1 : 0;
for(i = 1; i < 7; i++) Diffuser_Power[i] = Power_On;
/* 전원 OFF : OFF 표현은 Fan_Mode==0 && Run_Mode==MODE_VENTILATION(0).
Set_* 만 0 으로 두면 Run_Mode/Fan_Mode 가 이전값(유효모드/풍량)으로 남아
Fan_Speed_process 의 OFF 분기가 성립 안 돼 모터가 Power_On 을 다시 켬 → 직접 0 설정 */
if(Power_On == 0){ Set_Run_Mode = Run_Mode = MODE_VENTILATION; Set_Fan_Mode = Fan_Mode = 0; }
else
{
/* 전원 ON : 환기 모드 + 풍량 1단 (ERVSimulator HomeNetProtocol 와 동일) */
Set_Run_Mode = Run_Mode = MODE_VENTILATION;
Set_Fan_Mode = Fan_Mode = 1;
Fan_Speed_Setting(Run_Mode, Fan_Mode); /* 즉시 반영 (모드전환과 동일) */
}
/* 전원 토글 시 수동 댐퍼/LED 해제 → 자동 추종 복귀 */
for(i = 1; i <= 4; i++) { Diffuser_Damper_Manual[i] = 0; Diffuser_Led_Manual[i] = 0; }
/* 각실분배기 : 전원 비트(TYPE_POWER) + 모드/풍량. 룸컨(RJ2) : 별도 래치로 확실히 푸시 */
Command_request_type |= (TYPE_POWER | TYPE_MODE | TYPE_FAN_SPEED);
Homenet_RJ_Request |= (TYPE_MODE | TYPE_FAN_SPEED);
} else result = 1;
break;
case 0x02: /* CTRL_RUNMODE [mode] 0off/1환기/2자동/3공청/4바이패스 */
if(len >= 1)
{
if(pl[0] == 0) Power_On = 0;
else
{
Power_On = 1;
switch(pl[0])
{
case 1: Set_Run_Mode = MODE_VENTILATION; break;
case 2: Set_Run_Mode = MODE_AUTO; break;
case 3: Set_Run_Mode = MODE_AIRCLEAN; break;
case 4: Set_Run_Mode = MODE_BYPASS; break;
default: result = 1; break;
}
if(result == 0)
{
Run_Mode = Set_Run_Mode;
Command_request_type |= TYPE_MODE;
Homenet_RJ_Request |= TYPE_MODE; /* 룸컨이 새 모드 덮어쓰지 않게 푸시 */
/* 운전모드 전환 시 풍량 1단 (자동 제외 - 자동은 부하점수로 결정) */
if(Set_Run_Mode != MODE_AUTO)
{
Set_Fan_Mode = Fan_Mode = 1;
Command_request_type |= TYPE_FAN_SPEED;
Homenet_RJ_Request |= TYPE_FAN_SPEED;
/* 즉시 반영 : 풍량 단수(Total_Air_Volume)가 안 바뀌는 모드전환
(예: 환기1단→공청1단)에서도 새 모드 VSP가 걸리도록 CTRL_FAN 과 동일하게 직접 호출 */
Fan_Speed_Setting(Run_Mode, Fan_Mode);
}
}
}
/* 모드 전환 시 수동 댐퍼 해제 → 새 모드는 기본(전실 개방)에서 시작 */
for(i = 1; i <= 4; i++) Diffuser_Damper_Manual[i] = 0;
} else result = 1;
break;
case 0x03: /* CTRL_FAN [speed] */
if(len >= 1)
{
Set_Fan_Mode = Fan_Mode = pl[0];
Command_request_type |= TYPE_FAN_SPEED;
Homenet_RJ_Request |= TYPE_FAN_SPEED; /* 룸컨이 새 풍량 덮어쓰지 않게 푸시 */
if(Run_Mode != MODE_AUTO) Fan_Speed_Setting(Run_Mode, Fan_Mode); /* 즉시 반영(자동은 부하점수로 결정) */
}
else result = 1;
break;
case 0x04: /* CTRL_SUBMODE [type][onoff] 1수면/2조리/3회복 */
if(len >= 2)
{
if(pl[0] == 1) Ext_Run_Mode = pl[1] ? 4 : 0;
else if(pl[0] == 2) Hood_YeunDong_Enable = pl[1] ? 1 : 0;
else if(pl[0] == 3){ Ext_Run_Mode = pl[1] ? 1 : 0; Ext_Select_Room = 2; }
else result = 1;
/* 부가모드 변경 시 수동 댐퍼 해제 (수동 댐퍼는 환기/공청/바이패스에서만) */
if(result == 0) for(i = 1; i <= 4; i++) Diffuser_Damper_Manual[i] = 0;
} else result = 1;
break;
case 0x05: /* CTRL_HOOD [onoff] */
if(len >= 1) Hood_YeunDong_Enable = pl[0] ? 1 : 0; else result = 1;
break;
case 0x06: /* CTRL_HYST_PRESET [preset] */
if(len >= 1 && pl[0] < 3) Hyst_Preset = pl[0]; else result = 1;
break;
case 0x07: /* CTRL_HYST_VALUE [preset][pm25][pm10][voc][co2] u16 BE */
if(len >= 9 && pl[0] < 3)
{
uint8_t ps = pl[0];
Pm25_Db[ps] = (uint16_t)(((uint16_t)pl[1] << 8) | pl[2]);
Pm10_Db[ps] = (uint16_t)(((uint16_t)pl[3] << 8) | pl[4]);
Voc_Db[ps] = (uint16_t)(((uint16_t)pl[5] << 8) | pl[6]);
Co2_Db[ps] = (uint16_t)(((uint16_t)pl[7] << 8) | pl[8]);
} else result = 1;
break;
case 0x08: /* CTRL_DAMPER [room][type 0급기SA/1배기EA][onoff] — 수동 댐퍼(비자동에서 위치 유지) */
if(len >= 3 && pl[0] >= 1 && pl[0] <= 4)
{
uint8_t ang = pl[2] ? 110 : 0;
if(pl[1] == 0) Memory_Diffuser_Dmp_Ang_SA[pl[0]] = Diffuser_Dmp_Ang_SA[pl[0]] = ang; /* 급기 */
else if(pl[1] == 1) Memory_Diffuser_Dmp_Ang_RA[pl[0]] = Diffuser_Dmp_Ang_RA[pl[0]] = ang; /* 배기 */
else result = 1;
if(result == 0) Diffuser_Damper_Manual[pl[0]] = 1; /* 자동로직 덮어쓰기 차단(자동/모드전환 시 해제) */
} else result = 1;
break;
case 0x09: /* CTRL_LED [room][dim] — 수동 LED : 자동 추종 해제하고 지정값 유지(모든 모드, 전원OFF만 해제) */
if(len >= 2 && pl[0] >= 1 && pl[0] <= 4) { Light_Bright[pl[0]] = pl[1]; Diffuser_Led_Manual[pl[0]] = 1; }
else result = 1;
break;
case 0x0A: /* REQ_STATUS : 응답은 호출부에서 */
break;
case 0x0B: /* CTRL_RESET [onoff] */
if(len >= 1) Homenet_Reset_State = pl[0] ? 1 : 0; else result = 1;
break;
case 0x0C: /* CTRL_VSP [group][index][sa(2)][ea(2)] */
if(len >= 6)
result = hn_set_vsp(pl[0], pl[1],
(uint8_t)(((uint16_t)pl[2] << 8) | pl[3]),
(uint8_t)(((uint16_t)pl[4] << 8) | pl[5]));
else result = 1;
break;
case 0x0D: /* CTRL_HYST_THR [preset][pollutant 0CO2/1PM2.5/2PM10/3VOC][L1~L4 u16] */
if(len >= 10 && pl[0] < 3 && pl[1] < 4)
{
uint8_t ps = pl[0], g = pl[1], k;
uint16_t *arr = 0;
if(g == 0) arr = Co2_Thr[ps];
else if(g == 1) arr = Pm25_Thr[ps];
else if(g == 2) arr = Pm10_Thr[ps];
else if(g == 3) arr = Voc_Thr[ps];
if(arr) { for(k = 0; k < 4; k++) arr[k] = (uint16_t)(((uint16_t)pl[2 + k*2] << 8) | pl[3 + k*2]); }
else result = 1;
}
else result = 1;
break;
case 0x0E: /* CTRL_RESERVE [hours 0~8] : N시간 후 전원 OFF (0=해제) */
if(len >= 1 && pl[0] <= 8) Reserve_Remain_Sec = (uint16_t)(pl[0] * 3600);
else result = 1;
break;
default: result = 1; break;
}
/* 히스테리시스 프리셋/데드밴드/임계, VSP 변경은 EEPROM 영속화 */
if(result == 0 && (cmd == 0x06 || cmd == 0x07 || cmd == 0x0C || cmd == 0x0D))
EEP_Save_Flag = 1;
ack[0] = cmd; ack[1] = result;
Homenet_Send_Frame(HN_ACK, ack, 2);
}
/* 메인 루프(빠른 주기)에서 호출 : 수신 명령 처리 + REQ_STATUS 즉시 응답 */
void Homenet_Process(void)
{
static uint8_t status_buf[HN_STATUS_LEN];
if(Hn_cmd_ready)
{
uint8_t cmd = Hn_cmd, pl[HN_DATA_LEN]; uint16_t i;
for(i = 0; i < HN_DATA_LEN; i++) pl[i] = Hn_cmd_pl[i];
Hn_cmd_ready = 0;
hn_apply_cmd(cmd, pl, HN_DATA_LEN);
if(cmd == 0x0A) /* REQ_STATUS */
{
Homenet_Build_Status(status_buf);
Homenet_Send_Frame(HN_STATUS, status_buf, HN_STATUS_LEN);
}
}
}
/* 1초 주기 STATUS 자동 송신 */
void Homenet_Send_Status(void)
{
static uint8_t status_buf[HN_STATUS_LEN];
Homenet_Build_Status(status_buf);
Homenet_Send_Frame(HN_STATUS, status_buf, HN_STATUS_LEN);
}
void UART1_HANDLE()
{
uint8_t u8InChar=0xFF;
uint32_t u32IntSts= UART1->ISR;
if(u32IntSts & UART_ISR_RDA_IS_Msk)
{
u8InChar = UART_READ(UART1); /* Rx trigger level is 1 byte*/
Homenet_Rx_Byte(u8InChar); // HOMENET(ErvDashboard) 바이너리 프레임 수신
}
if(u32IntSts & UART_ISR_THRE_IS_Msk)
{
;
}
}
void UART1_IRQHandler(void)
{
UART1_HANDLE();
}