esp-idf/examples/bluetooth/esp_ble_mesh/aligenie_demo/components/vendor_model/genie_timer.c

846 wiersze
22 KiB
C

// Copyright (C) 2018-2020 Alibaba Group Holding Limited
// Adaptations to ESP-IDF Copyright (c) 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "genie_mesh.h"
#include "genie_event.h"
#include "genie_util.h"
#include "genie_timer.h"
#include "genie_model_srv.h"
#include "ble_mesh_example_nvs.h"
#define DAY 86400
#define HOUR 3600
#define MINU 60
#define VT_NUM (40)
#define VT_LOCK util_semaphore_take(&g_genie_timer.lock, -1)
#define VT_UNLOCK util_semaphore_give(&g_genie_timer.lock)
extern nvs_handle_t NVS_HANDLE;
static const char *TAG = "genie_timer";
static utc_time_t local_time = {0};
typedef enum {
TIMER_OFF = 0,
TIMER_ON = 1,
TIMER_INVAILD = 0xf,
} vt_state;
struct genie_timer_t {
genie_snode_t next;
uint8_t index;
uint8_t state: 4;
uint8_t periodic: 1;
uint16_t periodic_time;
uint8_t schedule;
uint32_t unixtime_match;
genie_timer_attr_data_t attr_data;
};
struct unixtime_sync_para_t {
uint16_t period_time; // Synchronization period: Time synchronization request cycle.
uint8_t retry_delay; // Retry delay: How long to retry after a failed time synchronization request
uint8_t retry_times; // Retry times of time synchronization request
};
struct {
uint16_t magic;
int8_t timezone;
struct unixtime_sync_para_t timing_sync_config; // Time synchronization parameters
struct genie_timer_t timer_data[VT_NUM]; // Store Timing operation
} g_timing_data;
struct {
uint32_t init: 1;
uint32_t update: 1;
esp_timer_handle_t timer;
struct k_work work;
util_semaphore_t lock;
genie_slist_t timer_list_active;
genie_slist_t timer_list_idle;
uint32_t unix_time;
uint32_t unix_time_sync_match;
uint8_t unix_time_sync_retry_times;
genie_timer_event_func_t cb;
} g_genie_timer;
static inline uint8_t is_leap_year(uint16_t year);
utc_time_t genie_timer_local_time_get(void)
{
return local_time;
}
uint32_t genie_timer_local_unixtime_get(void)
{
return g_genie_timer.unix_time;
}
static inline utc_time_t convert_unix_to_utc(uint32_t unix_time)
{
uint16_t g_noleap_daysbeforemonth[13] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
utc_time_t utc = {0};
uint32_t epoch = 0;
uint32_t jdn = 0;
int year = 0;
int month = 0;
int day = 0;
int hour = 0;
int minutes = 0;
int sec = 0;
int weekday = 0;
epoch = unix_time;
jdn = epoch / DAY;
epoch -= DAY * jdn;
weekday = (jdn + 4) % 7; // 1970/1/1 is thursday
hour = epoch / HOUR;
epoch -= HOUR * hour;
minutes = epoch / MINU;
epoch -= MINU * minutes;
sec = epoch;
year = jdn / (4 * 365 + 1); /* Number of 4-years periods since the epoch */
jdn -= year * (4 * 365 + 1); /* Remaining days */
year <<= 2; /* Years since the epoch */
/* Then we will brute force the next 0-3 years */
bool leapyear;
int tmp;
for (; ;) {
/* Is this year a leap year (we'll need this later too) */
leapyear = is_leap_year(year + 1970);
/* Get the number of days in the year */
tmp = (leapyear ? 366 : 365);
/* Do we have that many days? */
if (jdn >= tmp) {
/* Yes.. bump up the year */
year++;
jdn -= tmp;
} else {
/* Nope... then go handle months */
break;
}
}
/* At this point, value has the year and days has number days into this year */
year += 1970;
/* Handle the month (zero based) */
int min = 0;
int max = 11;
int value = 0;
do {
/* Get the midpoint */
value = (min + max) >> 1;
/* Get the number of days that occurred before the beginning of the month
* following the midpoint.
*/
tmp = g_noleap_daysbeforemonth[value + 1];
if (value + 1 >= 2 && is_leap_year(leapyear)) {
tmp++;
}
/* Does the number of days before this month that equal or exceed the
* number of days we have remaining?
*/
if (tmp > jdn) {
/* Yes.. then the month we want is somewhere from 'min' and to the
* midpoint, 'value'. Could it be the midpoint?
*/
tmp = g_noleap_daysbeforemonth[value];
if (value >= 2 && is_leap_year(leapyear)) {
tmp++;
}
if (tmp > jdn) {
/* No... The one we want is somewhere between min and value-1 */
max = value - 1;
} else {
/* Yes.. 'value' contains the month that we want */
break;
}
} else {
/* No... The one we want is somwhere between value+1 and max */
min = value + 1;
}
/* If we break out of the loop because min == max, then we want value
* to be equal to min == max.
*/
value = min;
} while (min < max);
/* The selected month number is in value. Subtract the number of days in the
* selected month
*/
tmp = g_noleap_daysbeforemonth[value];
if (value >= 2 && is_leap_year(leapyear)) {
tmp++;
}
jdn -= tmp;
/* At this point, value has the month into this year (zero based) and days has
* number of days into this month (zero based)
*/
month = value; // zero based
day = jdn + 1; // one based
utc.year = year;
utc.month = month;
utc.day = day;
utc.weekday = weekday;
utc.hour = hour;
utc.minutes = minutes;
utc.seconds = sec;
return utc;
}
static inline uint32_t convert_utc_to_unix(utc_time_t *utc_time)
{
uint32_t days;
uint16_t g_noleap_daysbeforemonth[13] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
days = (utc_time->year - 1970) * 365;
days += (utc_time->year - 1969) >> 2;
days += g_noleap_daysbeforemonth[utc_time->month];
if (utc_time->month >= 2 && is_leap_year(utc_time->year)) {
days++;
}
days += utc_time->day - 1;
return ((days * 24 + utc_time->hour) * 60 + utc_time->minutes) * 60 + utc_time->seconds;
}
static inline uint8_t is_leap_year(uint16_t year)
{
if (((year % 4) == 0) && ((year % 100) != 0)) {
return 1;
} else if ((year % 400) == 0) {
return 1;
} else {
return 0;
}
}
static inline void month_update(void)
{
local_time.month++;
if (local_time.month >= 12) {
local_time.month = 0;
local_time.year++;
}
}
static inline void days_update(void)
{
uint8_t month_days_list[12] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
};
local_time.day++;
if (is_leap_year(local_time.year)) {
month_days_list[1] = 29;
}
uint8_t month_day = month_days_list[local_time.month];
if (local_time.day > month_day) {
local_time.day = 0;
month_update();
}
local_time.weekday = (local_time.weekday + 1) % 7;
}
static inline void hours_update(void)
{
local_time.hour++;
if (local_time.hour == 24) {
local_time.hour = 0;
days_update();
}
}
static inline void minutes_update(void)
{
local_time.minutes++;
if (local_time.minutes == 60) {
local_time.minutes = 0;
hours_update();
}
}
static inline void seconds_update(void)
{
local_time.seconds++;
if (local_time.seconds == 60) {
local_time.seconds = 0;
minutes_update();
}
}
static void genie_timer_update(void *args)
{
if (!g_genie_timer.update) {
ESP_LOGD(TAG, "g_genie_timer.update %d", g_genie_timer.update);
return;
}
g_genie_timer.unix_time += 1;
seconds_update();
k_work_submit(&g_genie_timer.work);
if (g_genie_timer.unix_time % 60 == 0) {
ESP_LOGI(TAG, "genie_timer_update %d", g_genie_timer.unix_time);
}
}
static inline uint8_t next_weekday_diff_get(uint8_t weekday_now, uint8_t schedule)
{
uint16_t schedule_tmp = 0;
if (weekday_now == 0) {
weekday_now = 7;
}
schedule_tmp = ((schedule | ((uint16_t) schedule << 7)) >> (weekday_now - 1)) & 0x7f;
uint8_t day_diff = 0;
while (day_diff < 7) {
if ((schedule_tmp >> day_diff) & 0x0001) {
break;
}
day_diff++;
}
return day_diff;
}
static inline uint8_t is_weekday_match(uint8_t weekday_now, uint8_t schedule)
{
uint8_t weekday_mask = weekday_now ? (uint8_t)(1 << (weekday_now - 1)) : (uint8_t)(1 << 6);
return (weekday_mask == (schedule & weekday_mask));
}
static inline uint8_t next_weekday(uint8_t weekday_now)
{
return (weekday_now + 1) % 7;
}
static int genie_timer_save(void)
{
#ifdef GENIE_VENDOR_TIMER_STORE
int ret = 0;
ret = ble_mesh_nvs_store(NVS_HANDLE, GENIE_STORE_VENDOR_TIMER, &g_timing_data, sizeof(g_timing_data));
if (ret) {
ESP_LOGI(TAG, "vendor timers save fail %d", ret);
}
return ret;
#else
return -1;
#endif
}
static int genie_timer_restore(void)
{
#ifdef GENIE_VENDOR_TIMER_STORE
int ret = 0;
bool exist = false;
memset(&g_timing_data, 0, sizeof(g_timing_data));
ret = ble_mesh_nvs_restore(NVS_HANDLE, GENIE_STORE_VENDOR_TIMER, &g_timing_data, sizeof(g_timing_data), &exist);
if (ret) {
ESP_LOGI(TAG, "vendor timers restore fail %d", ret);
return ret;
}
if (g_timing_data.magic != 0xABCD
|| g_timing_data.timezone < -12
|| g_timing_data.timezone > 12) {
ESP_LOGI(TAG, "vendor timers restore missmatch");
return -1;
}
for (int i = 0; i < VT_NUM; i++) {
if (g_timing_data.timer_data[i].state != TIMER_INVAILD) {
genie_slist_append(&g_genie_timer.timer_list_active, &g_timing_data.timer_data[i].next);
} else {
genie_slist_append(&g_genie_timer.timer_list_idle, &g_timing_data.timer_data[i].next);
}
ESP_LOGI(TAG, "restore vendor timer index %d state %d periodic %d periodic_time %d schedule %d unixtime_match %d",
g_timing_data.timer_data[i].index, g_timing_data.timer_data[i].state,
g_timing_data.timer_data[i].periodic, g_timing_data.timer_data[i].periodic_time,
g_timing_data.timer_data[i].schedule, g_timing_data.timer_data[i].unixtime_match);
}
return 0;
#else
return -1;
#endif
}
static uint8_t is_genie_timer_timeout(struct genie_timer_t *vendor_timer)
{
if (vendor_timer->state == TIMER_INVAILD) {
return 0;
}
if (vendor_timer->periodic) {
if (is_weekday_match(local_time.weekday, vendor_timer->schedule)
&& vendor_timer->unixtime_match < g_genie_timer.unix_time) {
vendor_timer->unixtime_match += (1 + next_weekday_diff_get(next_weekday(local_time.weekday), vendor_timer->schedule)) * DAY;
}
}
return vendor_timer->unixtime_match == g_genie_timer.unix_time;
}
static void genie_timer_check(void)
{
struct genie_timer_t *tmp, *node;
GENIE_SLIST_FOR_EACH_CONTAINER_SAFE(&g_genie_timer.timer_list_active, node, tmp, next) {
if (is_genie_timer_timeout(node)) {
if (g_genie_timer.cb) {
g_genie_timer.cb(GENIE_TIME_EVT_TIMEOUT, node->index, &node->attr_data);
}
VT_LOCK;
if (!node->periodic) {
node->unixtime_match = 0xffffffff;
node->state = TIMER_INVAILD;
genie_slist_find_and_remove(&g_genie_timer.timer_list_active, &node->next);
genie_slist_append(&g_genie_timer.timer_list_idle, &node->next);
} else {
node->unixtime_match += 24 * HOUR;
}
VT_UNLOCK;
genie_timer_save();
}
}
if (g_genie_timer.unix_time_sync_match
&& g_genie_timer.unix_time_sync_match <= g_genie_timer.unix_time) {
if (g_genie_timer.cb) {
int ret = g_genie_timer.cb(GENIE_TIME_EVT_TIMING_SYNC, 0, NULL);
if (ret) {
if (g_genie_timer.unix_time_sync_retry_times > 0) {
g_genie_timer.unix_time_sync_match += g_timing_data.timing_sync_config.retry_delay * MINU;
g_genie_timer.unix_time_sync_retry_times--;
return;
}
}
}
g_genie_timer.unix_time_sync_match = g_genie_timer.unix_time + g_timing_data.timing_sync_config.period_time * MINU;
g_genie_timer.unix_time_sync_retry_times = g_timing_data.timing_sync_config.retry_times;
}
}
static void genie_timer_check_work(struct k_work *work)
{
genie_timer_check();
}
static genie_timer_handle_t genie_timer_find(uint8_t index)
{
ENTER_FUNC();
if (index >= VT_NUM) {
return NULL;
}
VT_LOCK;
struct genie_timer_t *tmp, *node = NULL;
genie_slist_t *list = NULL;
list = &g_genie_timer.timer_list_active;
GENIE_SLIST_FOR_EACH_CONTAINER_SAFE(list, node, tmp, next) {
if (node->index == index) {
VT_UNLOCK;
return node;
}
}
VT_UNLOCK;
return NULL;
}
static struct genie_timer_t *genie_timer_new(void)
{
ENTER_FUNC();
struct genie_timer_t *free_timer = NULL;
VT_LOCK;
free_timer = (struct genie_timer_t *)genie_slist_get(&g_genie_timer.timer_list_idle);
VT_UNLOCK;
ESP_LOGI(TAG, "timer new %p", free_timer);
return free_timer;
}
int genie_timer_start(uint8_t index, uint32_t unix_time, genie_timer_attr_data_t *attr_data)
{
ENTER_FUNC();
struct genie_timer_t *vendor_timer = NULL;
if (!attr_data) {
return -GENIE_TIMER_ERR_PARAM;
}
ESP_LOGI(TAG, "timer start index %d expect unix_time %d attr_type %d",
index, unix_time, attr_data->type);
if (!g_genie_timer.init) {
return -GENIE_TIMER_ERR_INIT;
}
if (!g_genie_timer.update) {
return -GENIE_TIMER_ERR_LOCALTIME_NOTSET;
}
if (index >= VT_NUM) {
//return -GENIE_TIMER_ERR_INDEX;
}
if (unix_time <= g_genie_timer.unix_time) {
return -GENIE_TIMER_ERR_PARAM;
}
vendor_timer = genie_timer_find(index);
if (vendor_timer == NULL) {
vendor_timer = genie_timer_new();
if (vendor_timer == NULL) {
return -GENIE_TIMER_ERR_NORESOURCE;
}
} else {
VT_LOCK;
genie_slist_find_and_remove(&g_genie_timer.timer_list_active, &vendor_timer->next);
VT_UNLOCK;
}
vendor_timer->index = index;
vendor_timer->unixtime_match = unix_time; // + g_genie_timer.timezone * HOUR;
vendor_timer->state = TIMER_ON;
vendor_timer->attr_data.type = attr_data->type;
vendor_timer->attr_data.para = attr_data->para;
VT_LOCK;
genie_slist_append(&g_genie_timer.timer_list_active, &vendor_timer->next);
VT_UNLOCK;
genie_timer_save();
return 0;
}
int genie_timer_periodic_start(uint8_t index, uint16_t periodic_time, uint8_t schedule, genie_timer_attr_data_t *attr_data)
{
ENTER_FUNC();
struct genie_timer_t *vendor_timer = NULL;
ESP_LOGI(TAG, "periodic timer start index %d periodic_time %d schedule %d attr_para %d",
index, periodic_time, schedule, attr_data->para);
if (!g_genie_timer.init) {
return -GENIE_TIMER_ERR_INIT;
}
if (!g_genie_timer.update) {
return -GENIE_TIMER_ERR_LOCALTIME_NOTSET;
}
if (index >= VT_NUM) {
//return -GENIE_TIMER_ERR_INDEX;
}
if (schedule == 0) {
return -GENIE_TIMER_ERR_PARAM;
}
vendor_timer = genie_timer_find(index);
if (vendor_timer == NULL) {
vendor_timer = genie_timer_new();
if (vendor_timer == NULL) {
return -GENIE_TIMER_ERR_NORESOURCE;
}
} else {
VT_LOCK;
genie_slist_find_and_remove(&g_genie_timer.timer_list_active, &vendor_timer->next);
VT_UNLOCK;
}
vendor_timer->index = index;
vendor_timer->periodic = 1;
vendor_timer->periodic_time = periodic_time;
vendor_timer->schedule = schedule;
vendor_timer->state = TIMER_ON;
utc_time_t utc = local_time;
utc.hour = 0;
utc.minutes = 0;
utc.seconds = 0;
utc.day = utc.day + next_weekday_diff_get(local_time.weekday, schedule);
vendor_timer->unixtime_match = convert_utc_to_unix(&utc) + periodic_time - g_timing_data.timezone * HOUR;
ESP_LOGI(TAG, "periodic timer unixtime_match %d", vendor_timer->unixtime_match);
VT_LOCK;
genie_slist_append(&g_genie_timer.timer_list_active, &vendor_timer->next);
VT_UNLOCK;
genie_timer_save();
return 0;
}
static int genie_timer_stop(int8_t index)
{
ENTER_FUNC();
ESP_LOGI(TAG, "timer stop %d", index);
if (!g_genie_timer.init) {
return -GENIE_TIMER_ERR_INIT;
}
if (!g_genie_timer.update) {
return -GENIE_TIMER_ERR_LOCALTIME_NOTSET;
}
if (index >= VT_NUM) {
return -GENIE_TIMER_ERR_INDEX;
}
struct genie_timer_t *vendor_timer = genie_timer_find(index);
if (vendor_timer == NULL) {
return -GENIE_TIMER_ERR_INDEX;
} else {
VT_LOCK;
vendor_timer->index = 0xFF;
vendor_timer->state = TIMER_INVAILD;
vendor_timer->unixtime_match = 0;
genie_slist_find_and_remove(&g_genie_timer.timer_list_active, &vendor_timer->next);
genie_slist_append(&g_genie_timer.timer_list_idle, &vendor_timer->next);
VT_UNLOCK;
}
return 0;
}
int genie_timer_remove(uint8_t index)
{
ENTER_FUNC();
int i = 0;
int ret = 0;
ESP_LOGI(TAG, "timer remove %d", index);
/* remove alll timers */
if (index == 0xFF) {
for (i = 0; i < VT_NUM; i++) {
genie_timer_stop(i);
}
return 0;
}
ret = genie_timer_stop(index);
genie_timer_save();
return ret;
}
void genie_timer_local_time_show(void)
{
ENTER_FUNC();
ESP_LOGI(TAG, "unix_time revert %d", convert_utc_to_unix(&local_time));
ESP_LOGI(TAG, "%4d/%2d/%2d %2d:%2d:%d weekday %2d %04d",
local_time.year, local_time.month + 1, local_time.day,
local_time.hour, local_time.minutes, local_time.seconds,
local_time.weekday, g_timing_data.timezone);
}
int genie_timer_timezone_update(int8_t timezone)
{
ENTER_FUNC();
ESP_LOGI(TAG, "timezone update %d", timezone);
if (timezone < -12 || timezone > 12) {
return -GENIE_TIMER_ERR_PARAM;
}
if (!g_genie_timer.init) {
return -GENIE_TIMER_ERR_INIT;
}
g_timing_data.timezone = timezone;
return 0;
}
int8_t genie_timer_timezone_get(void)
{
ENTER_FUNC();
return g_timing_data.timezone;
}
int genie_timer_time_sync_set(uint16_t period_time, uint8_t retry_delay, uint8_t retry_times)
{
ENTER_FUNC();
ESP_LOGI(TAG, "timing sync set period_time %d retry_delay %d retry_times %d",
period_time, retry_delay, retry_times);
if (period_time == 0 || retry_delay == 0 || retry_times) {
return -GENIE_TIMER_ERR_PARAM;
}
g_timing_data.timing_sync_config.period_time = period_time;
g_timing_data.timing_sync_config.retry_delay = retry_delay;
g_timing_data.timing_sync_config.retry_times = retry_times;
g_genie_timer.unix_time_sync_match = g_genie_timer.unix_time + g_timing_data.timing_sync_config.period_time * MINU;
g_genie_timer.unix_time_sync_retry_times = retry_times;
return 0;
}
int genie_timer_time_sync_get(uint16_t *period_time, uint8_t *retry_delay, uint8_t *retry_times)
{
ENTER_FUNC();
*period_time = g_timing_data.timing_sync_config.period_time;
*retry_delay = g_timing_data.timing_sync_config.retry_delay;
*retry_times = g_timing_data.timing_sync_config.retry_times;
return 0;
}
int genie_timer_local_time_update(uint32_t unix_time)
{
ENTER_FUNC();
if (!g_genie_timer.init) {
ESP_LOGE(TAG, "g_genie_timer not init");
return -GENIE_TIMER_ERR_INIT;
}
g_genie_timer.update = 1;
g_genie_timer.unix_time = unix_time;
local_time = convert_unix_to_utc(unix_time + g_timing_data.timezone * HOUR);
ESP_LOGI(TAG, "unix_time %d", unix_time);
ESP_LOGI(TAG, "localtime update %4d/%2d/%2d %2d:%2d:%d weekday %2d",
local_time.year, local_time.month + 1, local_time.day,
local_time.hour, local_time.minutes, local_time.seconds,
local_time.weekday);
ESP_LOGI(TAG, "unix_time revert %d", convert_utc_to_unix(&local_time));
return 0;
}
int genie_timer_init(genie_timer_event_func_t cb)
{
ENTER_FUNC();
int i = 0;
if (g_genie_timer.init) {
return 0;
}
if (cb == NULL) {
return -GENIE_TIMER_ERR_INIT;
}
memset(&g_genie_timer, 0, sizeof(g_genie_timer));
memset(&local_time, 0, sizeof(local_time));
g_genie_timer.cb = cb;
genie_slist_init(&g_genie_timer.timer_list_active);
genie_slist_init(&g_genie_timer.timer_list_idle);
util_semaphore_init(&g_genie_timer.lock, 1, 1);
k_work_init(&g_genie_timer.work, genie_timer_check_work);
esp_timer_create_args_t create_args = {
.callback = genie_timer_update,
.name = "genie_timer"
};
ESP_ERROR_CHECK(esp_timer_create(&create_args, &g_genie_timer.timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(g_genie_timer.timer, 1000 * 1000));
g_genie_timer.init = 1;
if (genie_timer_restore()) {
memset(&g_timing_data, 0, sizeof(g_timing_data));
g_timing_data.timezone = 8;
g_timing_data.magic = 0xABCD;
for (i = 0; i < VT_NUM; i++) {
g_timing_data.timer_data[i].unixtime_match = 0xffffffff;
g_timing_data.timer_data[i].index = 0xFF;
g_timing_data.timer_data[i].state = TIMER_INVAILD;
genie_slist_append(&g_genie_timer.timer_list_idle, &g_timing_data.timer_data[i].next);
}
}
/* sync timing */
// g_genie_timer.cb(GENIE_TIME_EVT_TIMING_SYNC, 0, NULL);
return 0;
}