Files
kunlun/app/smart_grid/sta/iot_sg_sta_ext_tm.c
2024-09-28 14:24:04 +08:00

561 lines
19 KiB
C

/****************************************************************************
Copyright(c) 2019 by Aerospace C.Power (Chongqing) Microelectronics. ALL RIGHTS RESERVED.
This Information is proprietary to Aerospace C.Power (Chongqing) Microelectronics and MAY NOT
be copied by any method or incorporated into another program without
the express written consent of Aerospace C.Power. This Information or any portion
thereof remains the property of Aerospace C.Power. The Information contained herein
is believed to be accurate and Aerospace C.Power assumes no responsibility or
liability for its use in any way and conveys no license or title under
any patent or copyright and makes no representation or warranty that this
Information is free from patent or copyright infringement.
****************************************************************************/
/* os_shim header files */
#include "os_utils_api.h"
/* iot common header files */
#include "iot_io_api.h"
#include "iot_oem_api.h"
/* smart grid internal header files */
#include "iot_sg.h"
#include "iot_sg_sta.h"
#include "proto_645.h"
#include "proto_69845.h"
#if IOT_SMART_GRID_EXT_TM_FUNC_ENABLE
/* define time management auto handle interval time. unit is 1s */
#define IOT_SG_STA_AUTO_TM_INTERVAL (3600 * 25)
/* define max delta threshold to save pib, unit is 1min */
#define IOT_SG_STA_MAX_DELTA_THRESHOLD (1023)
/* define correct time delta time, uint is 1s. */
#define IOT_SG_STA_CORRECT_TIME_DELTA_MAX (30 * 60)
#define IOT_SG_STA_CORRECT_TIME_DELTA_MIN (5 * 60)
#define IOT_SG_STA_CORRECT_TIME_DELTA_STOP (5)
#define IOT_SG_STA_CORRECT_TIME_DELTA_FIX (4 * 60 + 30)
#define IOT_SG_STA_CORRECT_TIME_DELTA_MAX_HLJ (5 * 60)
#define IOT_SG_STA_CORRECT_TIME_DELTA_MIN_HLJ (5)
#define IOT_SG_STA_CORRECT_TIME_DELTA_HOUR_MAX (9999)
/* define auto correct time way */
#define IOT_SG_STA_AUTO_CT_WAY_INVALID (0)
#define IOT_SG_STA_AUTO_CT_WAY_FIX_TIME (1)
#define IOT_SG_STA_AUTO_CT_WAY_CUR_TIME (2)
void iot_sg_sta_ext_auto_tm_init(uint8_t *addr)
{
uint8_t user_type = iot_sg_sta_get_user_type();
iot_sg_sta_global_t *sta_glb = p_sg_glb->desc.sta;
iot_sg_sta_app_info_t *sta_pib = iot_sg_sta_get_rw_pib();
iot_sg_sta_tm_info_t *auto_tm = &sta_glb->tm_info;
if (user_type != USER_TYPE_STATE_GRID_HLJ &&
user_type != USER_TYPE_STATE_GRID_GANSU &&
user_type != USER_TYPE_SOUTHEN_POWER_GRID_SHENZHEN) {
goto out;
}
os_mem_set(auto_tm, 0, sizeof(*auto_tm));
if (user_type == USER_TYPE_STATE_GRID_HLJ) {
auto_tm->threshold = 5 * 60;
auto_tm->rpt_flag = 1;
}
if (!iot_mac_addr_valid(addr)) {
goto out;
}
if (!iot_mac_addr_valid(sta_pib->pm_addr)) {
goto out;
}
if (!iot_mac_addr_cmp(addr, sta_pib->pm_addr)) {
goto out;
}
auto_tm->auto_flag = sta_pib->auto_corr;
/* time management delta threshold init */
if (user_type == USER_TYPE_STATE_GRID_HLJ) {
if (sta_pib->delta_mode) {
auto_tm->threshold = sta_pib->delta_threshold * 60;
}
}
iot_sg_printf("%s auto_flag %lu, threshold %lu s\n", __FUNCTION__,
auto_tm->auto_flag, auto_tm->threshold);
out:
return;
}
void iot_sg_sta_ext_auto_tm_update(void)
{
uint8_t user_type = iot_sg_sta_get_user_type();
iot_sg_sta_global_t *sta_glb = p_sg_glb->desc.sta;
iot_sg_sta_tm_info_t *auto_tm = &sta_glb->tm_info;
if (user_type != USER_TYPE_STATE_GRID_HLJ &&
user_type != USER_TYPE_STATE_GRID_GANSU &&
user_type != USER_TYPE_SOUTHEN_POWER_GRID_SHENZHEN) {
return;
}
if (auto_tm->auto_delay) {
auto_tm->auto_delay--;
if ((auto_tm->auto_delay % 3600) == 0) {
iot_sg_printf("%s left auto_delay %luh\n", __FUNCTION__,
auto_tm->auto_delay / 3600);
}
}
if (auto_tm->rpt_delay) {
auto_tm->rpt_delay--;
if ((auto_tm->rpt_delay % 3600) == 0) {
iot_sg_printf("%s left rpt_delay %luh\n", __FUNCTION__,
auto_tm->rpt_delay / 3600);
}
}
}
static uint8_t iot_sg_sta_ext_auto_ct_interval_check(int64_t interval,
uint8_t *flag_stop)
{
uint8_t ct_way = IOT_SG_STA_AUTO_CT_WAY_INVALID;
uint8_t user_type = iot_sg_sta_get_user_type();
if (user_type == USER_TYPE_STATE_GRID_HLJ) {
if (IOT_ABS(interval) > IOT_SG_STA_CORRECT_TIME_DELTA_MAX_HLJ) {
ct_way = IOT_SG_STA_AUTO_CT_WAY_FIX_TIME;
} else if (IOT_ABS(interval) > IOT_SG_STA_CORRECT_TIME_DELTA_MIN_HLJ){
ct_way = IOT_SG_STA_AUTO_CT_WAY_CUR_TIME;
}
goto out;
} else {
if (IOT_ABS(interval) <= IOT_SG_STA_CORRECT_TIME_DELTA_MAX &&
IOT_ABS(interval) >= IOT_SG_STA_CORRECT_TIME_DELTA_MIN) {
ct_way = IOT_SG_STA_AUTO_CT_WAY_FIX_TIME;
} else if (IOT_ABS(interval) >= IOT_SG_STA_CORRECT_TIME_DELTA_STOP &&
IOT_ABS(interval) < IOT_SG_STA_CORRECT_TIME_DELTA_MIN) {
ct_way = IOT_SG_STA_AUTO_CT_WAY_CUR_TIME;
}
if (ct_way == IOT_SG_STA_AUTO_CT_WAY_INVALID ||
user_type == USER_TYPE_SOUTHEN_POWER_GRID_GX) {
/* GX auto ct execute only once */
*flag_stop = 1;
}
goto out;
}
out:
return ct_way;
}
static int64_t iot_sg_sta_ext_delay_fix_get()
{
int64_t delay_fix;
uint8_t user_type = iot_sg_sta_get_user_type();
if (user_type == USER_TYPE_STATE_GRID_HLJ) {
delay_fix = IOT_SG_STA_CORRECT_TIME_DELTA_MAX_HLJ;
} else {
delay_fix = IOT_SG_STA_CORRECT_TIME_DELTA_FIX;
}
return delay_fix;
}
static uint32_t iot_sg_sta_ext_cache_time_get(iot_time_tm_t *tm)
{
uint32_t ret = ERR_OK;
int64_t delta_time;
iot_sg_sta_global_t *sta_glb = p_sg_glb->desc.sta;
if (!tm || !iot_time_valid(&sta_glb->tm_info.cache_time) ||
!sta_glb->tm_info.cache_time_ts) {
ret = ERR_FAIL;
goto out;
}
*tm = sta_glb->tm_info.cache_time;
delta_time = (os_boot_time64() / 1000) - sta_glb->tm_info.cache_time_ts;
iot_rtc_delta_add(delta_time, tm);
os_mem_set(&sta_glb->tm_info.cache_time, 0x0,
sizeof(sta_glb->tm_info.cache_time));
sta_glb->tm_info.cache_time_ts = 0;
out:
return ret;
}
static uint32_t iot_sg_sta_ext_auto_ct(iot_time_tm_t *tm, uint8_t *flag_stop)
{
uint8_t addr[IOT_MAC_ADDR_LEN], ct_way;
uint8_t user_type = iot_sg_sta_get_user_type();
uint32_t ret = ERR_FAIL;
int64_t interval, delay_fix;
iot_pkt_t *pkt;
iot_time_tm_t sys_tm, curr_tm;
proto_645_corr_time_t time;
server_addr_info_t server_addr = {0};
proto_69845_app_piid_t piid;
iot_sg_sta_node_desc_t *node;
iot_sg_sta_global_t *sta_glb = p_sg_glb->desc.sta;
iot_mac_addr_cpy(addr, p_sg_glb->plc_state.addr);
iot_mac_addr_reverse(addr);
node = iot_sg_sta_node_find_by_addr(addr);
if (node == NULL) {
goto out;
}
curr_tm = *tm;
if (user_type == USER_TYPE_SOUTHEN_POWER_GRID_GX) {
if (iot_sg_sta_ext_cache_time_get(&sys_tm) != ERR_OK) {
goto out;
}
} else {
if (iot_sg_sta_rtc_get(&sys_tm, 1) != ERR_OK) {
goto out;
}
}
iot_sg_printf("%s sys time %02d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
sys_tm.tm_year, sys_tm.tm_mon, sys_tm.tm_mday,
sys_tm.tm_hour, sys_tm.tm_min, sys_tm.tm_sec);
interval = iot_rtc_delta_calc(&curr_tm, &sys_tm);
ct_way = iot_sg_sta_ext_auto_ct_interval_check(interval, flag_stop);
if (ct_way == IOT_SG_STA_AUTO_CT_WAY_FIX_TIME) {
delay_fix = iot_sg_sta_ext_delay_fix_get();
if (interval > 0) {
iot_rtc_delta_add(delay_fix, &curr_tm);
} else {
iot_rtc_delta_add(0 - delay_fix, &curr_tm);
}
} else if (ct_way == IOT_SG_STA_AUTO_CT_WAY_CUR_TIME){
curr_tm = sys_tm;
} else {
goto out;
}
if (node->data_type == IOT_SG_STA_DATA_TYPE_69845) {
server_addr.type = PROTO_69845_SA_TYPE_BROADCAST;
server_addr.len = 1;
server_addr.addr[0] = PROTO_69845_SA_BROADCAST_ADD;
server_addr.logical_addr = 0;
piid.priority = PROTO_69845_APP_PIID_PRIORITY_GENERAL;
piid.sn = 0;
piid.reserved = 0;
if (sta_glb->tm_info.tm_mode_698 ==
IOT_SG_STA_698_CORR_TIME_MODE_NO_RN) {
pkt = proto_69845_build_corr_msg(&server_addr, &curr_tm, &piid, 0);
} else {
pkt = proto_69845_build_corr_msg(&server_addr, &curr_tm, &piid, 1);
}
if (pkt == NULL) {
goto out;
}
if (sta_glb->drv->correct_time(pkt, 0) == ERR_OK) {
sta_glb->tm_info.tm_mode_698++;
if (sta_glb->tm_info.tm_mode_698 >
IOT_SG_STA_698_CORR_TIME_MODE_WITH_RN) {
sta_glb->tm_info.tm_mode_698 = 0;
ret = ERR_OK;
}
}
} else {
time.year = iot_byte_to_bcd((uint8_t)(curr_tm.tm_year - 2000));
time.month = iot_byte_to_bcd(curr_tm.tm_mon);
time.day = iot_byte_to_bcd(curr_tm.tm_mday);
time.hour = iot_byte_to_bcd(curr_tm.tm_hour);
time.minute = iot_byte_to_bcd(curr_tm.tm_min);
time.second = iot_byte_to_bcd(curr_tm.tm_sec);
pkt = proto_645_build_corr_msg(NULL, &time);
if (pkt == NULL) {
goto out;
}
ret = sta_glb->drv->correct_time(pkt, 0);
}
iot_sg_printf("%s time %02d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
curr_tm.tm_year, curr_tm.tm_mon, curr_tm.tm_mday,
curr_tm.tm_hour, curr_tm.tm_min, curr_tm.tm_sec);
out:
return ret;
}
uint32_t iot_sg_sta_ext_clock_skew_report_hn(uint8_t new_evt, int64_t interval)
{
uint32_t ret = ERR_FAIL;
uint8_t length, addr[IOT_MAC_ADDR_LEN];
iot_pkt_t *pkt = NULL;
iot_sg_sta_global_t *sta_glb = p_sg_glb->desc.sta;
iot_sg_sta_evt_global_t *evt = &sta_glb->evt;
iot_time_tm_t time = { 0 };
proto_645_07_clock_skew_t *clock_skew;
proto_645_header_t *hdr = NULL;
if (iot_sg_sta_get_user_type() != USER_TYPE_STATE_GRID_HUNAN) {
goto out;
}
if (evt->retry_cnt >= 3) {
iot_sg_printf("%s retry end, cnt %d\n", __FUNCTION__, evt->retry_cnt);
iot_sg_module_report_reset_status(0);
goto out;
}
if (interval == 0) {
goto out;
}
length = sizeof(proto_645_header_t) + sizeof(proto_645_tailer_t) +
sizeof(*clock_skew);
pkt = iot_pkt_alloc(length, IOT_SMART_GRID_MID);
if (!pkt) {
goto out;
}
sta_glb->drv->get_login_addr(addr);
hdr = (proto_645_header_t *)iot_pkt_data(pkt);
proto_645_header_init(hdr, addr, PROTO_645_2007_FN_CLOCK_SKEW,
PROTO_645_DIR_SLAVE, PROTO_645_ACK_NORMAL, PROTO_645_FOLLOW_INVALID);
hdr->len = sizeof(*clock_skew);
clock_skew = (proto_645_07_clock_skew_t *)hdr->data;
if (interval > 0) {
clock_skew->mode = PROTO_645_CS_MODE_METER_BELOW_CCTT;
} else {
clock_skew->mode = PROTO_645_CS_MODE_METER_ABOVE_CCTT;
}
time.tm_year = 2000;
time.tm_mon = 1;
time.tm_mday = 1;
iot_rtc_delta_add(IOT_ABS(interval), &time);
clock_skew->time.year = iot_byte_to_bcd((uint8_t)(time.tm_year - 2000));
clock_skew->time.month = iot_byte_to_bcd(time.tm_mon - 1);
clock_skew->time.day = iot_byte_to_bcd(time.tm_mday - 1);
clock_skew->time.hour = iot_byte_to_bcd(time.tm_hour);
clock_skew->time.minute = iot_byte_to_bcd(time.tm_min);
clock_skew->time.second = iot_byte_to_bcd(time.tm_sec);
proto_645_add33_handle(hdr->data, hdr->len);
/* fill in 645 tailer */
proto_645_tail_init(hdr);
iot_pkt_put(pkt, length);
ret = iot_sg_sta_report_event_with_type(addr, (uint8_t *)hdr, length,
new_evt, IOT_SG_STA_RPT_MODULE_CLOCK_EVT);
if (ret == ERR_OK) {
sta_glb->drv->disable_event_rpt();
evt->clock_interval = interval;
evt->retry_cnt++;
}
out:
if (pkt) {
iot_pkt_free(pkt);
}
return ret;
}
uint32_t iot_sg_sta_ext_clock_skew_report_hlj(uint8_t new_evt,
int64_t interval)
{
uint8_t sec, min, length, addr[IOT_MAC_ADDR_LEN];
uint16_t hour;
uint32_t ret = ERR_FAIL, di = PROTO_645_2007_DI_DELTA_RPT, hour_cache;
iot_pkt_t *pkt = NULL;
iot_sg_sta_global_t *sta_glb = p_sg_glb->desc.sta;
iot_sg_sta_evt_global_t *evt = &sta_glb->evt;
proto_645_header_t *hdr = NULL;
proto_645_07_delta_rpt_t *rpt;
if (iot_sg_sta_get_user_type() != USER_TYPE_STATE_GRID_HLJ) {
goto out;
}
if (evt->retry_cnt >= 3) {
iot_sg_printf("%s retry end, cnt %d\n", __FUNCTION__, evt->retry_cnt);
iot_sg_module_report_reset_status(0);
goto out;
}
if (interval == 0) {
goto out;
}
sec = (uint8_t)(IOT_ABS(interval) % 60);
min = (uint8_t)((IOT_ABS(interval) / 60) % 60);
hour_cache = (uint32_t)(IOT_ABS(interval) / 3600);
if (hour_cache > IOT_SG_STA_CORRECT_TIME_DELTA_HOUR_MAX) {
hour = IOT_SG_STA_CORRECT_TIME_DELTA_HOUR_MAX;
min = 59;
sec = 59;
} else {
hour = (uint16_t)hour_cache;
}
length = sizeof(proto_645_header_t) + sizeof(proto_645_tailer_t) +
PROTO_645_2007_DI_LEN + sizeof(*rpt);
pkt = iot_pkt_alloc(length, IOT_SMART_GRID_MID);
if (!pkt) {
goto out;
}
sta_glb->drv->get_login_addr(addr);
hdr = (proto_645_header_t *)iot_pkt_data(pkt);
proto_645_header_init(hdr, addr, PROTO_645_2007_FN_READ_DATA,
PROTO_645_DIR_SLAVE, PROTO_645_ACK_NORMAL, PROTO_645_FOLLOW_INVALID);
hdr->len = sizeof(*rpt) + PROTO_645_2007_DI_LEN;
proto_645_2007_di_to_byte(di, hdr->data);
rpt = (proto_645_07_delta_rpt_t *)(hdr->data + PROTO_645_2007_DI_LEN);
if (interval > 0) {
rpt->mode = PROTO_645_CS_MODE_METER_BELOW_CCTT_HLJ;
} else {
rpt->mode = PROTO_645_CS_MODE_METER_ABOVE_CCTT_HLJ;
}
rpt->sec = iot_byte_to_bcd(sec);
rpt->min = iot_byte_to_bcd(min);
rpt->hour_high = iot_byte_to_bcd((uint8_t)(hour / 100));
rpt->hour_low = iot_byte_to_bcd((uint8_t)(hour % 100));
proto_645_add33_handle(hdr->data, hdr->len);
/* fill in 645 tailer */
proto_645_tail_init(hdr);
iot_pkt_put(pkt, length);
ret = iot_sg_sta_report_event_with_type(addr, (uint8_t *)hdr, length,
new_evt, IOT_SG_STA_RPT_MODULE_CLOCK_EVT);
if (ret == ERR_OK) {
sta_glb->drv->disable_event_rpt();
evt->clock_interval = interval;
evt->retry_cnt++;
iot_sg_printf("%s cnt %d\n", __FUNCTION__, evt->retry_cnt);
}
out:
if (pkt) {
iot_pkt_free(pkt);
}
return ret;
}
static uint32_t iot_sg_sta_ext_clock_skew(iot_time_tm_t *tm)
{
uint8_t user_type = iot_sg_sta_get_user_type();
uint32_t delta, flag = 0, ret = ERR_FAIL;
int64_t interval;
iot_time_tm_t sys_tm, curr_tm;
iot_sg_sta_global_t *sta_glb = p_sg_glb->desc.sta;
iot_sg_sta_tm_info_t *auto_tm = &sta_glb->tm_info;
curr_tm = *tm;
if (user_type == USER_TYPE_STATE_GRID_HUNAN) {
if (iot_sg_sta_ext_cache_time_get(&sys_tm) != ERR_OK) {
goto out;
}
} else {
if (iot_sg_sta_rtc_get(&sys_tm, 1) != ERR_OK) {
goto out;
}
}
interval = iot_rtc_delta_calc(&curr_tm, &sys_tm);
delta = (uint32_t)IOT_ABS(interval);
if (interval >= 0) {
flag = 1;
}
iot_sg_printf("%s sys time %02d-%02d-%02d %02d:%02d:%02d, delta %lu, "
"flag %lu\n", __FUNCTION__, sys_tm.tm_year, sys_tm.tm_mon,
sys_tm.tm_mday, sys_tm.tm_hour, sys_tm.tm_min, sys_tm.tm_sec,
delta, flag);
if ((IOT_ABS(interval) < auto_tm->threshold) || !auto_tm->threshold) {
goto out;
}
if (user_type == USER_TYPE_STATE_GRID_HLJ) {
ret = iot_sg_sta_ext_clock_skew_report_hlj(1, interval);
} else if (user_type == USER_TYPE_STATE_GRID_HUNAN) {
ret = iot_sg_sta_ext_clock_skew_report_hn(1, interval);
}
out:
return ret;
}
void iot_sg_sta_ext_save_auto_correct_pib(uint8_t *flag_auto,
uint16_t *delta_threshold, uint8_t *addr)
{
uint8_t ref, reason, flag_write = 0;
uint16_t ticket;
iot_sg_sta_app_info_t *sta_pib = iot_sg_sta_get_rw_pib();
if (iot_sg_sta_get_user_type() != USER_TYPE_STATE_GRID_HLJ) {
goto out;
}
if (!iot_mac_addr_valid(addr)) {
reason = 1;
goto drop;
}
if (!sta_pib) {
reason = 2;
goto drop;
}
if (flag_auto && sta_pib->auto_corr != *flag_auto) {
flag_write = 1;
}
if (delta_threshold && sta_pib->delta_threshold != *delta_threshold) {
if (*delta_threshold > IOT_SG_STA_MAX_DELTA_THRESHOLD) {
reason = 3;
goto drop;
}
flag_write = 1;
}
if (flag_write) {
iot_pib_acquire_app_commit_ref(&ref);
if (flag_auto) {
sta_pib->auto_corr = *flag_auto;
}
if (delta_threshold) {
sta_pib->delta_mode = 1;
sta_pib->delta_threshold = *delta_threshold;
}
iot_mac_addr_cpy(sta_pib->pm_addr, addr);
iot_pib_release_app_commit_ref(&ref);
iot_pib_app_commit(&ticket);
iot_sg_printf("%s auto %lu, threshold %lu\n", __FUNCTION__,
sta_pib->auto_corr, sta_pib->delta_threshold);
}
goto out;
drop:
iot_sg_printf("%s err %lu\n", __FUNCTION__, reason);
out:
return;
}
void iot_sg_sta_ext_auto_ct_set(uint8_t enable, uint8_t delay_clean)
{
uint8_t user_type = iot_sg_sta_get_user_type();
iot_sg_sta_global_t *sta_glb = p_sg_glb->desc.sta;
iot_sg_sta_tm_info_t *auto_tm = &sta_glb->tm_info;
auto_tm->auto_flag = enable;
if (delay_clean) {
auto_tm->auto_delay = 0;
}
if (user_type == USER_TYPE_STATE_GRID_HLJ ||
user_type == USER_TYPE_SOUTHEN_POWER_GRID_SHENZHEN ||
user_type == USER_TYPE_STATE_GRID_GANSU) {
iot_sg_sta_ext_save_auto_correct_pib(&auto_tm->auto_flag, NULL,
p_sg_glb->plc_state.addr);
}
}
void iot_sg_sta_ext_auto_tm_handle(iot_time_tm_t *tm)
{
uint8_t user_type = iot_sg_sta_get_user_type(), flag_stop = 0;
iot_sg_sta_global_t *sta_glb = p_sg_glb->desc.sta;
iot_sg_sta_tm_info_t *auto_tm = &sta_glb->tm_info;
if (user_type != USER_TYPE_STATE_GRID_HLJ &&
user_type != USER_TYPE_STATE_GRID_GANSU &&
user_type != USER_TYPE_STATE_GRID_HUNAN &&
user_type != USER_TYPE_SOUTHEN_POWER_GRID_GX &&
user_type != USER_TYPE_SOUTHEN_POWER_GRID_SHENZHEN) {
goto out;
}
if (auto_tm->auto_flag && !auto_tm->auto_delay) {
if (iot_sg_sta_ext_auto_ct(tm, &flag_stop) == ERR_OK) {
auto_tm->auto_delay = IOT_SG_STA_AUTO_TM_INTERVAL;
}
if (flag_stop) {
iot_sg_sta_ext_auto_ct_set(0, 1);
}
}
if (auto_tm->rpt_flag && !auto_tm->rpt_delay) {
if (iot_sg_sta_ext_clock_skew(tm) == ERR_OK) {
auto_tm->rpt_delay = IOT_SG_STA_AUTO_TM_INTERVAL;
}
if (user_type == USER_TYPE_STATE_GRID_HUNAN) {
/* HUNAN clock skew execute only once */
auto_tm->rpt_flag = 0;
auto_tm->rpt_delay = 0;
}
}
out:
return;
}
#endif /* IOT_SMART_GRID_EXT_TM_FUNC_ENABLE */