diff --git a/include/arke-avr.h b/include/arke-avr.h index abaa82a..15ff6d4 100644 --- a/include/arke-avr.h +++ b/include/arke-avr.h @@ -1,61 +1,65 @@ #pragma once #include "arke-avr/systime.h" #include #include "yaacl.h" #include "inttypes.h" void InitArke(uint8_t * rxBuffer, uint8_t length); #define ARKE_NO_MESSAGE 0 yaacl_idt_t ArkeProcess(uint8_t * length); uint8_t ArkeMyID(); void ArkeSoftwareReset(); #include #define implements_ArkeSoftwareReset() \ void ArkeSoftwareReset() { \ wdt_enable(WDTO_15MS); \ for(;;){} \ } \ void wdt_init() __attribute__((naked)) __attribute__((section(".init3"))); \ void wdt_init() { \ MCUSR = 0; \ wdt_disable(); \ } +typedef uint16_t ArkeError_t; + +void ArkeReportError(ArkeError_t error); + #define ARKE_DECLARE_SENDER_FUNCTION(name) \ yaacl_error_e ArkeSend ## name(yaacl_txn_t * txn, bool emergency,const Arke ## name * data) ARKE_DECLARE_SENDER_FUNCTION(ZeusSetPoint); ARKE_DECLARE_SENDER_FUNCTION(ZeusReport); ARKE_DECLARE_SENDER_FUNCTION(ZeusConfig); ARKE_DECLARE_SENDER_FUNCTION(ZeusStatus); ARKE_DECLARE_SENDER_FUNCTION(ZeusControlPoint); ARKE_DECLARE_SENDER_FUNCTION(ZeusDeltaTemperature); ARKE_DECLARE_SENDER_FUNCTION(HeliosSetPoint); ARKE_DECLARE_SENDER_FUNCTION(CelaenoSetPoint); ARKE_DECLARE_SENDER_FUNCTION(CelaenoStatus); ARKE_DECLARE_SENDER_FUNCTION(CelaenoConfig); #define ArkeZeusSetPointClassValue ARKE_ZEUS_SET_POINT #define ArkeZeusReportClassValue ARKE_ZEUS_REPORT #define ArkeZeusStatusClassValue ARKE_ZEUS_STATUS #define ArkeZeusConfigClassValue ARKE_ZEUS_CONFIG #define ArkeZeusControlPointClassValue ARKE_ZEUS_CONTROL_POINT #define ArkeZeusDeltaTemperatureClassValue ARKE_ZEUS_DELTA_TEMPERATURE #define ArkeHeliosSetPointClassValue ARKE_HELIOS_SET_POINT #define ArkeCelaenoSetPointClassValue ARKE_CELAENO_SET_POINT #define ArkeCelaenoStatusClassValue ARKE_CELAENO_STATUS #define ArkeCelaenoConfigClassValue ARKE_CELAENO_CONFIG #define ARKE_MESSAGE_STRUCT_TO_CLASS(name) ( name ## ClassValue) diff --git a/include/arke.h b/include/arke.h index c3adea6..84d7236 100644 --- a/include/arke.h +++ b/include/arke.h @@ -1,171 +1,172 @@ #pragma once #include "inttypes.h" #ifdef __cplusplus extern "C"{ #endif //__cplusplus typedef enum ArkeMessageType_e { ARKE_NETWORK_CONTROL_COMMAND = 0x00, ARKE_HIGH_PRIORITY_MESSAGE = 0x01, ARKE_MESSAGE = 0x02, ARKE_HEARTBEAT = 0x03, ARKE_MESSAGE_TYPE_MASK = 0x03 << 9 } ArkeMessageType; typedef enum ArkeNodeClass_e { ARKE_BROADCAST = 0x0, ARKE_ZEUS = 0x38, ARKE_HELIOS = 0x34, ARKE_CELAENO = 0x30, ARKE_NODE_CLASS_MASK = 0x3f << 3 } ArkeNodeClass; typedef enum ArkeNetworkCommand_e { ARKE_RESET_REQUEST = 0x00, ARKE_SYNCHRONISATION = 0x01, ARKE_ID_CHANGE_REQUEST = 0x02, + ARKE_ERROR_REPORT = 0x03, ARKE_HEARTBEAT_REQUEST = 0x07, ARKE_SUBID_MASK = 0x07 } ArkeNetworkCommand; //#define ARKE_SUBID_MASK ARKE_HEARTBEAT_REQUEST typedef enum ArkeMessageClass_e { ARKE_ZEUS_SET_POINT = 0x38, ARKE_ZEUS_REPORT = 0x39, ARKE_ZEUS_VIBRATION_REPORT = 0x3a, ARKE_ZEUS_CONFIG = 0x3b, ARKE_ZEUS_STATUS = 0x3c, ARKE_ZEUS_CONTROL_POINT = 0x3d, ARKE_ZEUS_DELTA_TEMPERATURE = 0x3e, ARKE_HELIOS_SET_POINT = 0x34, ARKE_HELIOS_PULSE_MODE = 0x35, ARKE_CELAENO_SET_POINT = 0x30, ARKE_CELAENO_STATUS = 0x31, ARKE_CELAENO_CONFIG = 0x32 } ArkeMessageClass; struct ArkeZeusSetPoint_t { uint16_t Humidity; uint16_t Temperature; uint8_t Wind; } __attribute__((packed)); typedef struct ArkeZeusSetPoint_t ArkeZeusSetPoint; struct ArkeZeusReport_t { uint16_t Humidity:14; uint16_t Temperature1:14; uint16_t Temperature2:12; uint16_t Temperature3:12; uint16_t Temperature4:12; } __attribute__((packed)); typedef struct ArkeZeusReport_t ArkeZeusReport; struct ArkePIDConfig_t { uint8_t ProportionalMult; uint8_t DerivativeMult; uint8_t IntegralMult; uint8_t DividerPower:4; uint8_t DividerPowerInt:4; } __attribute__((packed)); typedef struct ArkePIDConfig_t ArkePIDConfig; struct ArkeZeusConfig_t { ArkePIDConfig Humidity; ArkePIDConfig Temperature; } __attribute__((packed)); typedef struct ArkeZeusConfig_t ArkeZeusConfig; #define ARKE_FAN_AGING_ALERT (0x4000) #define ARKE_FAN_STALL_ALERT (0x8000) #define ARKE_FAN_RPM_MASK (0x3fff) #define ArkeFanAging(status) (((status).fanStatus & ARKE_FAN_AGING_ALERT) != 0x0000) #define ArkeFanStall(status) (((status).fanStatus & ARKE_FAN_STALL_ALERT) != 0x0000) #define ArkeFanRPM(status) ( (status).fanStatus & ARKE_FAN_RPM_MASK ) typedef uint16_t ArkeFanStatus; typedef enum ArkeZeusStatus_e { ARKE_ZEUS_IDLE = 0, ARKE_ZEUS_ACTIVE = (1 << 0), ARKE_ZEUS_CLIMATE_UNCONTROLLED_WD = (1 << 1), ARKE_ZEUS_HUMIDITY_UNREACHABLE = (1 << 2), ARKE_ZEUS_TEMPERATURE_UNREACHABLE = (1 << 3), } ArkeZeusStatus_e; struct ArkeZeusStatus_t { ArkeZeusStatus_e Status; ArkeFanStatus Fan[3]; } __attribute__((packed)); typedef struct ArkeZeusStatus_t ArkeZeusStatus; struct ArkeZeusControlPoint_t { int16_t Humidity; int16_t Temperature; } __attribute__((packed)); typedef struct ArkeZeusControlPoint_t ArkeZeusControlPoint; struct ArkeZeusDeltaTemperature_t { int16_t Delta[4]; } __attribute__((packed)); typedef struct ArkeZeusDeltaTemperature_t ArkeZeusDeltaTemperature; struct ArkeHeliosSetPoint_t { uint8_t Visible; uint8_t UV; } __attribute__((packed)); typedef struct ArkeHeliosSetPoint_t ArkeHeliosSetPoint; struct ArkeCelaenoSetPoint_t { uint8_t Power; } __attribute__((packed)); typedef struct ArkeCelaenoSetPoint_t ArkeCelaenoSetPoint; struct ArkeCelaenoStatus_t { uint8_t waterLevel; ArkeFanStatus fanStatus; } __attribute__((packed)); typedef struct ArkeCelaenoStatus_t ArkeCelaenoStatus; typedef enum ArkeCelaenoWaterLevel_e { ARKE_CELAENO_NOMINAL = 0, ARKE_CELAENO_WARNING = (1 << 0), ARKE_CELAENO_CRITICAL = (1 << 1), ARKE_CELAENO_RO_ERROR = (1 << 2) } ArkeCelaenoWaterLevel; #define ArkeCelaenoWaterNominal(status) ( (status).waterLevel == 0 ) #define ArkeCelaenoWaterWarning(status) ( (status).waterLevel == ARKE_CELAENO_WARNING ) #define ArkeCelaenoWaterCritical(status) ( ((status).waterLevel & ~(ARKE_CELAENO_CRITICAL | ARKE_CELAENO_RO_ERROR) ) == ARKE_CELAENO_WARNING ) #define ArkeCelaenoWaterHasRoError(status) (((status).waterLevel & ARKE_CELAENO_RO_ERROR) != 0x00) struct ArkeCelaenoConfig_t { uint16_t RampUpTimeMS; uint16_t RampDownTimeMS; uint16_t MinOnTimeMS; uint16_t DebounceTimeMS; } __attribute__((packed)); typedef struct ArkeCelaenoConfig_t ArkeCelaenoConfig; #ifdef __cplusplus } #endif //__cplusplus diff --git a/src-avr/arke.c b/src-avr/arke.c index eee0d83..026fe32 100644 --- a/src-avr/arke.c +++ b/src-avr/arke.c @@ -1,268 +1,345 @@ #include #include #include #include #include #if defined(ARKE_MY_FW_TWEAK_VERSION) #define VERSION_LENGTH 4 #elif defined(ARKE_MY_FW_PATCH_VERSION) #define VERSION_LENGTH 3 #else #define VERSION_LENGTH 2 #endif #ifndef ARKE_MY_SIZE #error "Please define ARKE_MY_SIZE" #else #define ARKE_RX_IDT(ID_) ( (ARKE_MESSAGE << 9) | (ARKE_MY_CLASS << 3) | (ID_)) #define ARKE_RX_IDT_MASK (ARKE_MESSAGE_TYPE_MASK | ((~(ARKE_MY_SIZE - 1) & 0x3f) << 3) | 0x07 ) #define ARKE_RX_BROAD_MASK (ARKE_MESSAGE_TYPE_MASK | ((~(ARKE_MY_SIZE - 1) & 0x3f) << 3)) #endif typedef enum ArkeHeartbeatStatus_e { NO_HEARTBEAT = 0, HEARTBEAT_ONCE = 1, HEARTBEAT_REPEAT = 2 } ArkeHeartbeatStatus_e; + +#define ARKE_ERROR_FIFO_SIZE 16 +#define ARKE_ERROR_FIFO_MASK 15 + +struct ArkeErrorFIFO_t { + ArkeError_t data[ARKE_ERROR_FIFO_SIZE]; + uint8_t size,head,tail; +}; + +#define FIFO_INIT(f) do { \ + (f).size = 0; \ + (f).head = 0; \ + (f).tail = 0; \ + }while(0) +#define FIFO_FULL(f) ((f).size >= ARKE_ERROR_FIFO_SIZE) +#define FIFO_EMPTY(f) ((f).size == 0) +#define FIFO_HEAD(f) ((f).data[(f).head]) +#define FIFO_TAIL(f) ((f).data[(f).tail]) +#define FIFO_INCR_PTR(p) (p) = ((p) + 1) & ARKE_ERROR_FIFO_MASK +#define FIFO_INCREMENT_HEAD(f) do{ \ + FIFO_INCR_PTR((f).head); \ + (f).size -= 1; \ + }while(0) +#define FIFO_INCREMENT_TAIL(f) do{ \ + FIFO_INCR_PTR((f).tail); \ + (f).size += 1; \ + }while(0) + + + typedef struct ArkeData_t { //we limit ourselfs to 6 MOb yaacl_txn_t control; - yaacl_txn_t heartbeat; + yaacl_txn_t heartbeat,error; yaacl_txn_t rx,broadcast; uint8_t rxLength; uint8_t * rxBuffer; uint8_t controlData[8]; uint8_t heartbeatData[VERSION_LENGTH]; + uint8_t errorData[4]; ArkeHeartbeatStatus_e heartbeatStatus; ArkeSystime_t heartbeatPeriod; ArkeSystime_t lastHeartbeat; uint8_t ID; + struct ArkeErrorFIFO_t errors; } ArkeData_t; #define arke_prepare_rx_txn(ID_) do { \ yaacl_make_std_idt(arke.rx.ID,ARKE_RX_IDT(ID_),0); \ yaacl_make_std_mask(arke.rx.mask,ARKE_RX_IDT_MASK,0,1); \ arke.rx.length = arke.rxLength; \ arke.rx.data = arke.rxBuffer; \ }while(0); #define arke_prepare_broadcast_txn(ID_) do { \ yaacl_make_std_idt(arke.broadcast.ID,ARKE_RX_IDT(ID_),0); \ yaacl_make_std_mask(arke.broadcast.mask,ARKE_RX_BROAD_MASK,0,1); \ arke.broadcast.length = arke.rxLength; \ arke.broadcast.data = arke.rxBuffer; \ }while(0); ArkeData_t arke; uint8_t EEMEM arkeID = 1; void InitArke(uint8_t * rxBuffer, uint8_t length) { uint8_t sreg = SREG; cli(); arke.ID = eeprom_read_byte(&arkeID); if ( arke.ID == 0 || arke.ID > 7 ) { arke.ID = 1; } SREG = sreg; arke.heartbeatData[0] = ARKE_MY_FW_MAJOR_VERSION; arke.heartbeatData[1] = ARKE_MY_FW_MINOR_VERSION; #if VERSION_LENGTH > 2 arke.heartbeatData[2] = ARKE_MY_FW_PATCH_VERSION; #endif #if VERSION_LENGTH > 3 arke.heartbeatData[3] = ARKE_MY_FW_TWEAK_VERSION; #endif arke.heartbeatStatus = NO_HEARTBEAT; arke.heartbeatPeriod = 0; ArkeInitSystime(); yaacl_config_t config; config.baudrate = YAACL_BR_250; yaacl_init(&config); yaacl_init_txn(&(arke.control)); + yaacl_init_txn(&(arke.error)); yaacl_init_txn(&(arke.heartbeat)); yaacl_init_txn(&(arke.rx)); //reserve immediatly the highest priority Mob for Network Control Handling yaacl_make_std_idt(arke.control.ID,0,0); yaacl_make_std_mask(arke.control.mask,ARKE_MESSAGE_TYPE_MASK,1,1); arke.control.length = 8; arke.control.data = &(arke.controlData[0]); + arke.error.ID = arke.ID; + arke.error.length = 4; + arke.error.data = &(arke.errorData[0]); + arke.error.data[0] = ARKE_MY_CLASS; + arke.error.data[1] = arke.ID; + arke.error.data[2] = 0; + arke.error.data[3] = 0; + + arke.heartbeat.ID = (ARKE_HEARTBEAT << 9 ) | (ARKE_MY_CLASS << 3) | arke.ID; arke.heartbeat.data = &(arke.heartbeatData[0]); arke.rxLength = length; arke.rxBuffer = rxBuffer; arke_prepare_rx_txn(arke.ID); arke_prepare_broadcast_txn(arke.ID); yaacl_listen(&arke.control); yaacl_listen(&arke.broadcast); yaacl_listen(&arke.rx); + FIFO_INIT(arke.errors); + } void ProcessControl() { uint8_t command = arke.control.ID & ARKE_SUBID_MASK; uint8_t dataLength = arke.control.length; // we are forbidden to access any yaacl function here if ( command == ARKE_RESET_REQUEST && dataLength == 1 ) { uint8_t targetID = arke.controlData[0]; if ( targetID == 0 || targetID == arke.ID) { ArkeSoftwareReset(); } return; } if ( command == ARKE_HEARTBEAT_REQUEST ) { if (dataLength == 0 || (dataLength == 2 && arke.controlData[0] == 0 && arke.controlData[1] == 0 ) ) { // single heartbeat request arke.heartbeatPeriod = 0; arke.lastHeartbeat = ArkeGetSystime(); arke.heartbeatStatus = HEARTBEAT_ONCE; } else if ( dataLength == 2 ) { *(((uint8_t*)(&arke.heartbeatPeriod)) + 0) = arke.controlData[0]; *(((uint8_t*)(&arke.heartbeatPeriod)) + 1) = arke.controlData[1]; arke.lastHeartbeat = ArkeGetSystime(); arke.heartbeatStatus = HEARTBEAT_REPEAT; } return; } if ( command == ARKE_ID_CHANGE_REQUEST && dataLength == 2 && arke.controlData[0] == arke.ID && arke.controlData[1] > 0 && arke.controlData[1] < 8 ) { arke.ID = arke.controlData[1]; uint8_t sreg = SREG; cli(); eeprom_update_byte(&arkeID,arke.ID); SREG = sreg; + + + // needed, but normally you won't change ID on the fly. + ArkeSoftwareReset(); return; } } yaacl_idt_t ArkeProcessNodeMessage(uint8_t * length) { yaacl_txn_status_e s = yaacl_txn_status(&arke.rx); yaacl_idt_t ret = ARKE_NO_MESSAGE; if ( s == YAACL_TXN_COMPLETED ) { *length = arke.rx.length; ret = arke.rx.ID; } if ( s != YAACL_TXN_PENDING ) { arke_prepare_rx_txn(arke.ID); yaacl_listen(&arke.rx); } if ( ret != ARKE_NO_MESSAGE) { return ret; } s = yaacl_txn_status(&arke.broadcast); if ( s == YAACL_TXN_COMPLETED ) { *length = arke.broadcast.length; ret = arke.broadcast.ID; } if ( s != YAACL_TXN_PENDING ) { arke_prepare_broadcast_txn(arke.ID); yaacl_listen(&arke.broadcast); } return ret; } +void arkeSendErrors() { + if (FIFO_EMPTY(arke.errors) || yaacl_txn_status(&(arke.error)) != YAACL_TXN_UNSUBMITTED ) { + return; + } + + ArkeError_t e = FIFO_HEAD(arke.errors); + arke.errorData[2] = e & 0x00ff; + arke.errorData[3] = (e & 0xff00) >> 8; + + yaacl_error_e err = yaacl_send(&(arke.error)); + if (err == YAACL_ERR_MOB_OVERFLOW) { + return; + } + + FIFO_INCREMENT_HEAD(arke.errors); +} + yaacl_idt_t ArkeProcess(uint8_t * length) { yaacl_txn_status_e s = yaacl_txn_status(&(arke.control)); if ( s == YAACL_TXN_COMPLETED ) { // ID and data will not change unless yaacl_txn_status is // called again. uint8_t targetID = (arke.control.ID & ARKE_NODE_CLASS_MASK) >> 3; if ( targetID == 0x00 || targetID == ARKE_MY_CLASS ) { ProcessControl(); } } if ( s != YAACL_TXN_PENDING ) { // we received or had an error, we re-listen arke.control.length = 8; yaacl_listen(&(arke.control)); } yaacl_idt_t toReturn = ArkeProcessNodeMessage(length); + arkeSendErrors(); + // heartbeat if ( arke.heartbeatStatus == NO_HEARTBEAT || yaacl_txn_status(&(arke.heartbeat)) != YAACL_TXN_UNSUBMITTED ) { //we are sending or just have sent an heartbeat, or we do not //need a periodic heartbeat return toReturn; } ArkeSystime_t now = ArkeGetSystime(); if ( (now - arke.lastHeartbeat) < arke.heartbeatPeriod ) { // wait before sending a new heartbeat return toReturn; } arke.heartbeat.length = (arke.heartbeatStatus == HEARTBEAT_ONCE) ? VERSION_LENGTH : 0; yaacl_error_e err = yaacl_send(&(arke.heartbeat)); if ( err == YAACL_ERR_MOB_OVERFLOW ) { // no free Mob, no worries, we fo it later. return toReturn; } //heartbeat succesfully submitted if (arke.heartbeatStatus == HEARTBEAT_ONCE ) { arke.heartbeatStatus = NO_HEARTBEAT; } else { arke.lastHeartbeat = now; } return toReturn; } #define implement_sender_function(name) \ ARKE_DECLARE_SENDER_FUNCTION(name) { \ txn->ID = arke.ID \ | ( Arke ## name ## ClassValue << 3) \ | ( (emergency ? ARKE_HIGH_PRIORITY_MESSAGE : ARKE_MESSAGE) << 9) ; \ txn->length = sizeof(Arke ## name); \ txn->data = (uint8_t*)data; \ return yaacl_send(txn); \ } implement_sender_function(ZeusSetPoint) implement_sender_function(ZeusReport) implement_sender_function(ZeusStatus) implement_sender_function(ZeusConfig) implement_sender_function(ZeusControlPoint) implement_sender_function(ZeusDeltaTemperature) implement_sender_function(HeliosSetPoint) implement_sender_function(CelaenoSetPoint) implement_sender_function(CelaenoStatus) implement_sender_function(CelaenoConfig) + + + +void ArkeReportError(ArkeError_t error) { + if ( FIFO_FULL(arke.errors) ) { + return; + } + FIFO_TAIL(arke.errors) = error; + FIFO_INCREMENT_TAIL(arke.errors); +}