1928 lines
56 KiB
C
1928 lines
56 KiB
C
|
/*
|
||
|
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||
|
*
|
||
|
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
|
||
|
*/
|
||
|
#include "system/includes.h"
|
||
|
#include "bt_common.h"
|
||
|
#include "api/sig_mesh_api.h"
|
||
|
#include <stdlib.h>
|
||
|
#include "api/properties.h"
|
||
|
#include "api/light_ctrl_srv.h"
|
||
|
#include "api/sensor.h"
|
||
|
#include "api/sensor_types.h"
|
||
|
#include "api/lightness_internal.h"
|
||
|
#include "api/gen_onoff_internal.h"
|
||
|
#include "api/light_ctrl_internal.h"
|
||
|
#include "model_types.h"
|
||
|
#include "model_utils.h"
|
||
|
#include "model_api.h"
|
||
|
|
||
|
#if (CONFIG_BT_MESH_LIGHT_CTRL_SRV)
|
||
|
|
||
|
#define LOG_TAG "[Gen_LC_srv]"
|
||
|
#define LOG_ERROR_ENABLE
|
||
|
#define LOG_DEBUG_ENABLE
|
||
|
#define LOG_INFO_ENABLE
|
||
|
/* #define LOG_DUMP_ENABLE */
|
||
|
#define LOG_CLI_ENABLE
|
||
|
#include "debug.h"
|
||
|
|
||
|
#define FLAGS_CONFIGURATION (BIT(FLAG_STARTED) | BIT(FLAG_OCC_MODE))
|
||
|
|
||
|
enum flags {
|
||
|
FLAG_ON,
|
||
|
FLAG_OCC_MODE,
|
||
|
FLAG_MANUAL,
|
||
|
FLAG_REGULATOR,
|
||
|
FLAG_OCC_PENDING,
|
||
|
FLAG_ON_PENDING,
|
||
|
FLAG_OFF_PENDING,
|
||
|
FLAG_TRANSITION,
|
||
|
FLAG_STORE_CFG,
|
||
|
FLAG_STORE_STATE,
|
||
|
FLAG_CTRL_SRV_MANUALLY_ENABLED,
|
||
|
FLAG_STARTED,
|
||
|
FLAG_RESUME_TIMER,
|
||
|
FLAG_AMBIENT_LUXLEVEL_SET,
|
||
|
};
|
||
|
|
||
|
enum stored_flags {
|
||
|
STORED_FLAG_ON = FLAG_ON,
|
||
|
STORED_FLAG_OCC_MODE = FLAG_OCC_MODE,
|
||
|
STORED_FLAG_ENABLED,
|
||
|
};
|
||
|
|
||
|
struct setup_srv_storage_data {
|
||
|
struct bt_mesh_light_ctrl_srv_cfg cfg;
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
struct bt_mesh_light_ctrl_reg_cfg reg_cfg;
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
static void restart_timer(struct bt_mesh_light_ctrl_srv *srv, uint32_t delay)
|
||
|
{
|
||
|
log_debug("delay: %d", delay);
|
||
|
k_work_reschedule(&srv->timer, K_MSEC(delay));
|
||
|
}
|
||
|
|
||
|
static inline uint32_t to_centi_lux(const struct sensor_value *lux)
|
||
|
{
|
||
|
return lux->val1 * 100L + lux->val2 / 10000L;
|
||
|
}
|
||
|
|
||
|
static inline void from_centi_lux(uint32_t centi_lux, struct sensor_value *lux)
|
||
|
{
|
||
|
lux->val1 = centi_lux / 100L;
|
||
|
lux->val2 = centi_lux % 10000L;
|
||
|
}
|
||
|
|
||
|
static void store(struct bt_mesh_light_ctrl_srv *srv, enum flags kind)
|
||
|
{
|
||
|
#if CONFIG_BT_SETTINGS
|
||
|
atomic_set_bit(&srv->flags, kind);
|
||
|
|
||
|
bt_mesh_model_data_store_schedule(srv->model);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static bool is_enabled(const struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
return srv->lightness->ctrl == srv;
|
||
|
}
|
||
|
|
||
|
static void schedule_resume_timer(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
if (srv->resume) {
|
||
|
k_work_reschedule(&srv->timer, K_SECONDS(srv->resume));
|
||
|
atomic_set_bit(&srv->flags, FLAG_RESUME_TIMER);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static uint32_t delay_remaining(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
return k_ticks_to_ms_ceil32(
|
||
|
k_work_delayable_remaining_get(&srv->action_delay));
|
||
|
}
|
||
|
|
||
|
static int delayed_change(struct bt_mesh_light_ctrl_srv *srv, bool value,
|
||
|
uint32_t delay)
|
||
|
{
|
||
|
if (!is_enabled(srv)) {
|
||
|
return -EBUSY;
|
||
|
}
|
||
|
|
||
|
if (((value && (atomic_test_bit(&srv->flags, FLAG_ON_PENDING) ||
|
||
|
atomic_test_bit(&srv->flags, FLAG_OCC_PENDING))) ||
|
||
|
(!value && atomic_test_bit(&srv->flags, FLAG_OFF_PENDING))) &&
|
||
|
(delay_remaining(srv) < delay)) {
|
||
|
/* Trying to do a second delayed change that will finish later
|
||
|
* with the same result. Can be safely ignored.
|
||
|
*/
|
||
|
return -EALREADY;
|
||
|
}
|
||
|
|
||
|
/* Clear all pending transitions - the last one triggered should be the
|
||
|
* one that gets executed. Note that the check above prevents this from
|
||
|
* delaying transitions.
|
||
|
*/
|
||
|
atomic_clear_bit(&srv->flags, FLAG_ON_PENDING);
|
||
|
atomic_clear_bit(&srv->flags, FLAG_OFF_PENDING);
|
||
|
atomic_clear_bit(&srv->flags, FLAG_OCC_PENDING);
|
||
|
|
||
|
k_work_reschedule(&srv->action_delay, K_MSEC(delay));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void delayed_occupancy(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
int32_t ms_since_motion)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
if (ms_since_motion > srv->cfg.occupancy_delay) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
log_debug("Turn On in %d ms", srv->cfg.occupancy_delay - ms_since_motion);
|
||
|
err = delayed_change(srv, true,
|
||
|
srv->cfg.occupancy_delay - ms_since_motion);
|
||
|
if (err) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
atomic_set_bit(&srv->flags, FLAG_OCC_PENDING);
|
||
|
}
|
||
|
|
||
|
static void delayed_set(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
const struct bt_mesh_model_transition *transition,
|
||
|
bool value)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
err = delayed_change(srv, value, transition->delay);
|
||
|
if (err) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
atomic_set_bit(&srv->flags, value ? FLAG_ON_PENDING : FLAG_OFF_PENDING);
|
||
|
|
||
|
srv->fade.duration = transition->time;
|
||
|
}
|
||
|
|
||
|
static void light_set(struct bt_mesh_light_ctrl_srv *srv, uint16_t lvl,
|
||
|
uint32_t transition_time)
|
||
|
{
|
||
|
struct bt_mesh_model_transition transition = {transition_time};
|
||
|
struct bt_mesh_lightness_set set = {
|
||
|
.lvl = lvl,
|
||
|
.transition = &transition,
|
||
|
};
|
||
|
struct bt_mesh_lightness_status dummy;
|
||
|
|
||
|
lightness_srv_change_lvl(srv->lightness, NULL, &set, &dummy, true);
|
||
|
}
|
||
|
|
||
|
static uint32_t remaining_fade_time(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
if (!atomic_test_bit(&srv->flags, FLAG_TRANSITION)) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return k_ticks_to_ms_ceil32(k_work_delayable_remaining_get(&srv->timer));
|
||
|
}
|
||
|
|
||
|
static uint32_t curr_fade_time(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
if (!atomic_test_bit(&srv->flags, FLAG_TRANSITION)) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
uint32_t remaining = remaining_fade_time(srv);
|
||
|
|
||
|
return srv->fade.duration > remaining ? srv->fade.duration - remaining : 0;
|
||
|
}
|
||
|
|
||
|
static bool state_is_on(const struct bt_mesh_light_ctrl_srv *srv,
|
||
|
enum bt_mesh_light_ctrl_srv_state state)
|
||
|
{
|
||
|
/* Only the stable Standby state is considered Off: */
|
||
|
if (srv->fade.duration > 0) {
|
||
|
return (state != LIGHT_CTRL_STATE_STANDBY ||
|
||
|
atomic_test_bit(&srv->flags, FLAG_TRANSITION));
|
||
|
}
|
||
|
|
||
|
return srv->state != LIGHT_CTRL_STATE_STANDBY;
|
||
|
}
|
||
|
|
||
|
static void light_onoff_encode(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
struct net_buf_simple *buf,
|
||
|
enum bt_mesh_light_ctrl_srv_state prev_state)
|
||
|
{
|
||
|
bt_mesh_model_msg_init(buf, BT_MESH_LIGHT_CTRL_OP_LIGHT_ONOFF_STATUS);
|
||
|
net_buf_simple_add_u8(buf, state_is_on(srv, prev_state));
|
||
|
|
||
|
uint32_t remaining_fade = remaining_fade_time(srv);
|
||
|
|
||
|
if (remaining_fade) {
|
||
|
net_buf_simple_add_u8(buf,
|
||
|
srv->state != LIGHT_CTRL_STATE_STANDBY);
|
||
|
net_buf_simple_add_u8(buf,
|
||
|
model_transition_encode(remaining_fade));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (atomic_test_bit(&srv->flags, FLAG_ON_PENDING) ||
|
||
|
atomic_test_bit(&srv->flags, FLAG_OFF_PENDING)) {
|
||
|
remaining_fade = srv->fade.duration;
|
||
|
net_buf_simple_add_u8(buf, atomic_test_bit(&srv->flags,
|
||
|
FLAG_ON_PENDING));
|
||
|
net_buf_simple_add_u8(buf,
|
||
|
model_transition_encode(remaining_fade));
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int light_onoff_status_send(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
struct bt_mesh_msg_ctx *ctx,
|
||
|
enum bt_mesh_light_ctrl_srv_state prev_state)
|
||
|
{
|
||
|
BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_LIGHT_CTRL_OP_LIGHT_ONOFF_STATUS,
|
||
|
3);
|
||
|
light_onoff_encode(srv, &buf, prev_state);
|
||
|
|
||
|
return bt_mesh_msg_send(srv->model, ctx, &buf);
|
||
|
}
|
||
|
|
||
|
static void onoff_encode(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
enum bt_mesh_light_ctrl_srv_state prev_state,
|
||
|
struct bt_mesh_onoff_status *status)
|
||
|
{
|
||
|
uint32_t remaining_fade;
|
||
|
|
||
|
if (atomic_test_bit(&srv->flags, FLAG_ON_PENDING) ||
|
||
|
atomic_test_bit(&srv->flags, FLAG_OFF_PENDING)) {
|
||
|
remaining_fade = srv->fade.duration;
|
||
|
status->target_on_off = atomic_test_bit(&srv->flags,
|
||
|
FLAG_ON_PENDING);
|
||
|
} else {
|
||
|
remaining_fade = remaining_fade_time(srv);
|
||
|
status->target_on_off = srv->state != LIGHT_CTRL_STATE_STANDBY;
|
||
|
}
|
||
|
|
||
|
status->present_on_off = state_is_on(srv, prev_state);
|
||
|
status->remaining_time = remaining_fade;
|
||
|
}
|
||
|
|
||
|
static void light_onoff_pub(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
enum bt_mesh_light_ctrl_srv_state prev_state,
|
||
|
bool pub_gen_onoff)
|
||
|
{
|
||
|
struct bt_mesh_onoff_status status;
|
||
|
|
||
|
(void)light_onoff_status_send(srv, NULL, prev_state);
|
||
|
|
||
|
if (!pub_gen_onoff) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
onoff_encode(srv, prev_state, &status);
|
||
|
|
||
|
(void)bt_mesh_onoff_srv_pub(&srv->onoff, NULL, &status);
|
||
|
}
|
||
|
|
||
|
static uint16_t light_get(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
if (!is_enabled(srv)) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (!atomic_test_bit(&srv->flags, FLAG_TRANSITION) ||
|
||
|
!srv->fade.duration) {
|
||
|
return srv->cfg.light[srv->state];
|
||
|
}
|
||
|
|
||
|
uint32_t curr = curr_fade_time(srv);
|
||
|
uint16_t start = srv->fade.initial_light;
|
||
|
uint16_t end = srv->cfg.light[srv->state];
|
||
|
|
||
|
/* Linear interpolation: */
|
||
|
return start + ((end - start) * curr) / srv->fade.duration;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static float sensor_to_float(struct sensor_value *val)
|
||
|
{
|
||
|
return val->val1 + val->val2 / 1000000.0f;
|
||
|
}
|
||
|
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
static void lux_get(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
struct sensor_value *lux)
|
||
|
{
|
||
|
if (!is_enabled(srv)) {
|
||
|
memset(lux, 0, sizeof(*lux));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!atomic_test_bit(&srv->flags, FLAG_TRANSITION) ||
|
||
|
!srv->fade.duration) {
|
||
|
*lux = srv->cfg.lux[srv->state];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
uint32_t delta = curr_fade_time(srv);
|
||
|
uint32_t init = to_centi_lux(&srv->fade.initial_lux);
|
||
|
uint32_t cfg = to_centi_lux(&srv->cfg.lux[srv->state]);
|
||
|
uint32_t centi_lux = init + ((cfg - init) * delta) / srv->fade.duration;
|
||
|
|
||
|
from_centi_lux(centi_lux, lux);
|
||
|
}
|
||
|
|
||
|
static float lux_getf(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
if (!is_enabled(srv)) {
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
return to_centi_lux(&srv->cfg.lux[srv->state]) / 100.0f;
|
||
|
}
|
||
|
|
||
|
static void reg_updated(struct bt_mesh_light_ctrl_reg *reg, float value)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = (struct bt_mesh_light_ctrl_srv *)(reg->user_data);
|
||
|
uint16_t output = CLAMP(value, 0, UINT16_MAX);
|
||
|
/* The regulator output is always in linear format. We'll convert to
|
||
|
* the configured representation again before calling the Lightness
|
||
|
* server.
|
||
|
*/
|
||
|
uint16_t lvl = to_linear(light_get(srv));
|
||
|
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_AMB_LIGHT_LEVEL_TIMEOUT
|
||
|
int64_t timestamp_temp;
|
||
|
|
||
|
timestamp_temp = srv->amb_light_level_timestamp;
|
||
|
if (k_uptime_delta(×tamp_temp) >=
|
||
|
CONFIG_BT_MESH_LIGHT_CTRL_AMB_LIGHT_LEVEL_TIMEOUT * MSEC_PER_SEC) {
|
||
|
srv->reg->measured = 0;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Output value is max out of regulator and configured level. */
|
||
|
if (output <= lvl) {
|
||
|
output = lvl;
|
||
|
}
|
||
|
|
||
|
if (!atomic_test_and_clear_bit(&srv->flags, FLAG_REGULATOR) &&
|
||
|
output == srv->reg_prev) {
|
||
|
return;
|
||
|
}
|
||
|
srv->reg_prev = output;
|
||
|
|
||
|
struct bt_mesh_lightness_set set = {
|
||
|
/* Regulator output is linear, but lightness works in the configured representation.
|
||
|
*/
|
||
|
.lvl = from_linear(output),
|
||
|
};
|
||
|
|
||
|
bt_mesh_lightness_srv_set(srv->lightness, NULL, &set,
|
||
|
&(struct bt_mesh_lightness_status) {});
|
||
|
}
|
||
|
|
||
|
#else
|
||
|
|
||
|
static void lux_get(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
struct sensor_value *lux)
|
||
|
{
|
||
|
memset(lux, 0, sizeof(*lux));
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
static void transition_start(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
enum bt_mesh_light_ctrl_srv_state state,
|
||
|
uint32_t fade_time)
|
||
|
{
|
||
|
srv->state = state;
|
||
|
srv->fade.initial_light = light_get(srv);
|
||
|
srv->fade.duration = fade_time;
|
||
|
lux_get(srv, &srv->fade.initial_lux);
|
||
|
|
||
|
atomic_set_bit(&srv->flags, FLAG_TRANSITION);
|
||
|
if (!IS_ENABLED(CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG) ||
|
||
|
!atomic_test_bit(&srv->flags, FLAG_AMBIENT_LUXLEVEL_SET)) {
|
||
|
/* If the regulator is enabled and Ambient LuxLevel value has been provided, the
|
||
|
* regulator will provide a light value to an application according to new state
|
||
|
* and target value.
|
||
|
*/
|
||
|
light_set(srv, srv->cfg.light[state], fade_time);
|
||
|
}
|
||
|
restart_timer(srv, fade_time);
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
if (srv->reg) {
|
||
|
atomic_set_bit(&srv->flags, FLAG_REGULATOR);
|
||
|
bt_mesh_light_ctrl_reg_target_set(srv->reg, lux_getf(srv), fade_time);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static int turn_on(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
const struct bt_mesh_model_transition *transition,
|
||
|
bool pub_gen_onoff)
|
||
|
{
|
||
|
if (!is_enabled(srv)) {
|
||
|
return -EBUSY;
|
||
|
}
|
||
|
|
||
|
uint32_t fade_time;
|
||
|
|
||
|
if (transition) {
|
||
|
fade_time = transition->time;
|
||
|
} else {
|
||
|
fade_time = srv->cfg.fade_on;
|
||
|
}
|
||
|
|
||
|
log_debug("Light Turned On in %d ms", fade_time);
|
||
|
|
||
|
if (srv->state != LIGHT_CTRL_STATE_ON) {
|
||
|
enum bt_mesh_light_ctrl_srv_state prev_state = srv->state;
|
||
|
|
||
|
if (prev_state == LIGHT_CTRL_STATE_STANDBY) {
|
||
|
atomic_set_bit(&srv->flags, FLAG_ON);
|
||
|
atomic_clear_bit(&srv->flags, FLAG_MANUAL);
|
||
|
store(srv, FLAG_STORE_STATE);
|
||
|
}
|
||
|
|
||
|
transition_start(srv, LIGHT_CTRL_STATE_ON, fade_time);
|
||
|
light_onoff_pub(srv, prev_state, pub_gen_onoff);
|
||
|
} else if (!atomic_test_bit(&srv->flags, FLAG_TRANSITION)) {
|
||
|
/* Delay the move to prolong */
|
||
|
restart_timer(srv, srv->cfg.on);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void turn_off_auto(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
if (!is_enabled(srv) || srv->state != LIGHT_CTRL_STATE_PROLONG) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
log_debug("Light Auto Turns Off in %d ms", srv->cfg.fade_standby_auto);
|
||
|
|
||
|
transition_start(srv, LIGHT_CTRL_STATE_STANDBY,
|
||
|
srv->cfg.fade_standby_auto);
|
||
|
|
||
|
atomic_clear_bit(&srv->flags, FLAG_ON);
|
||
|
store(srv, FLAG_STORE_STATE);
|
||
|
light_onoff_pub(srv, LIGHT_CTRL_STATE_PROLONG, true);
|
||
|
}
|
||
|
|
||
|
static int turn_off(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
const struct bt_mesh_model_transition *transition,
|
||
|
bool pub_gen_onoff)
|
||
|
{
|
||
|
if (!is_enabled(srv)) {
|
||
|
return -EBUSY;
|
||
|
}
|
||
|
|
||
|
log_debug("Light Turned Off");
|
||
|
|
||
|
uint32_t fade_time =
|
||
|
transition ? transition->time : srv->cfg.fade_standby_manual;
|
||
|
enum bt_mesh_light_ctrl_srv_state prev_state = srv->state;
|
||
|
|
||
|
if (prev_state != LIGHT_CTRL_STATE_STANDBY) {
|
||
|
atomic_set_bit(&srv->flags, FLAG_MANUAL);
|
||
|
transition_start(srv, LIGHT_CTRL_STATE_STANDBY, fade_time);
|
||
|
atomic_clear_bit(&srv->flags, FLAG_ON);
|
||
|
store(srv, FLAG_STORE_STATE);
|
||
|
light_onoff_pub(srv, prev_state, pub_gen_onoff);
|
||
|
} else if (atomic_test_bit(&srv->flags, FLAG_TRANSITION) &&
|
||
|
!atomic_test_bit(&srv->flags, FLAG_MANUAL)) {
|
||
|
atomic_set_bit(&srv->flags, FLAG_MANUAL);
|
||
|
transition_start(srv, LIGHT_CTRL_STATE_STANDBY, fade_time);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void prolong(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
if (!is_enabled(srv) || srv->state != LIGHT_CTRL_STATE_ON) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
log_debug("Light Fades to Prolong for %d ms", srv->cfg.fade_prolong);
|
||
|
|
||
|
transition_start(srv, LIGHT_CTRL_STATE_PROLONG, srv->cfg.fade_prolong);
|
||
|
}
|
||
|
|
||
|
static void ctrl_enable(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
atomic_clear_bit(&srv->flags, FLAG_RESUME_TIMER);
|
||
|
atomic_clear_bit(&srv->flags, FLAG_AMBIENT_LUXLEVEL_SET);
|
||
|
srv->lightness->ctrl = srv;
|
||
|
log_debug("Enable Light Control");
|
||
|
transition_start(srv, LIGHT_CTRL_STATE_STANDBY, 0);
|
||
|
/* Regulator remains stopped until fresh LuxLevel is received. */
|
||
|
}
|
||
|
|
||
|
static void reg_start(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
if (srv->reg && srv->reg->start) {
|
||
|
srv->reg->start(srv->reg, to_linear(light_get(srv)));
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static void reg_stop(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
if (srv->reg && srv->reg->stop) {
|
||
|
srv->reg->stop(srv->reg);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static void ctrl_disable(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
/* Do not change the light level, disabling the control server just
|
||
|
* disengages it from the Lightness Server.
|
||
|
*/
|
||
|
|
||
|
/* Clear transient state: */
|
||
|
atomic_and(&srv->flags, FLAGS_CONFIGURATION);
|
||
|
srv->lightness->ctrl = NULL;
|
||
|
srv->state = LIGHT_CTRL_STATE_STANDBY;
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
srv->reg_prev = 0;
|
||
|
#endif
|
||
|
|
||
|
log_debug("Disable Light Control");
|
||
|
|
||
|
/* If any of these cancel calls fail, their handler will exit early on
|
||
|
* their is_enabled() checks:
|
||
|
*/
|
||
|
k_work_cancel_delayable(&srv->action_delay);
|
||
|
k_work_cancel_delayable(&srv->timer);
|
||
|
|
||
|
reg_stop(srv);
|
||
|
|
||
|
light_onoff_pub(srv, srv->state, true);
|
||
|
}
|
||
|
|
||
|
/*******************************************************************************
|
||
|
* Timeouts
|
||
|
******************************************************************************/
|
||
|
|
||
|
static void timeout(struct k_work *work)
|
||
|
{
|
||
|
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
||
|
struct bt_mesh_light_ctrl_srv *srv =
|
||
|
CONTAINER_OF(dwork, struct bt_mesh_light_ctrl_srv, timer);
|
||
|
|
||
|
if (!is_enabled(srv)) {
|
||
|
if (srv->resume && atomic_test_and_clear_bit(&srv->flags, FLAG_RESUME_TIMER)) {
|
||
|
log_debug("Resuming LC server");
|
||
|
ctrl_enable(srv);
|
||
|
store(srv, FLAG_STORE_STATE);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* According to test spec, we should publish the OnOff state at the end
|
||
|
* of the transition:
|
||
|
*/
|
||
|
if (atomic_test_and_clear_bit(&srv->flags, FLAG_TRANSITION)) {
|
||
|
log_debug("Transition complete. State: %d", srv->state);
|
||
|
|
||
|
/* If the fade wasn't instant, we've already published the
|
||
|
* steady state in the state change function.
|
||
|
*/
|
||
|
if (srv->fade.duration > 0) {
|
||
|
// just for test
|
||
|
// light_onoff_pub(srv, srv->state, true);
|
||
|
light_onoff_pub(srv, srv->state, false);
|
||
|
}
|
||
|
|
||
|
if (srv->state == LIGHT_CTRL_STATE_ON) {
|
||
|
log_debug("Light stays On for %d ms", srv->cfg.on);
|
||
|
restart_timer(srv, srv->cfg.on);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (srv->state == LIGHT_CTRL_STATE_PROLONG) {
|
||
|
log_debug("Light in Prolong for %d ms", srv->cfg.prolong);
|
||
|
restart_timer(srv, srv->cfg.prolong);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* If we're in manual mode, wait until the end of the cooldown
|
||
|
* period before disabling it:
|
||
|
*/
|
||
|
if (!atomic_test_bit(&srv->flags, FLAG_MANUAL)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
uint32_t cooldown = MSEC_PER_SEC *
|
||
|
CONFIG_BT_MESH_LIGHT_CTRL_SRV_TIME_MANUAL;
|
||
|
|
||
|
if (srv->fade.duration >= cooldown) {
|
||
|
atomic_clear_bit(&srv->flags, FLAG_MANUAL);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
restart_timer(srv, cooldown - srv->fade.duration);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_BT_MESH_SCENE_SRV)) {
|
||
|
bt_mesh_scene_invalidate(srv->model);
|
||
|
}
|
||
|
|
||
|
if (srv->state == LIGHT_CTRL_STATE_ON) {
|
||
|
prolong(srv);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (srv->state == LIGHT_CTRL_STATE_PROLONG) {
|
||
|
turn_off_auto(srv);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Ends the sensor cooldown period: */
|
||
|
atomic_clear_bit(&srv->flags, FLAG_MANUAL);
|
||
|
}
|
||
|
|
||
|
static void delayed_action_timeout(struct k_work *work)
|
||
|
{
|
||
|
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
||
|
struct bt_mesh_light_ctrl_srv *srv = CONTAINER_OF(
|
||
|
dwork, struct bt_mesh_light_ctrl_srv, action_delay);
|
||
|
struct bt_mesh_model_transition transition = {
|
||
|
.time = srv->fade.duration
|
||
|
};
|
||
|
|
||
|
if (!is_enabled(srv)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
log_debug("Delayed Action Timeout");
|
||
|
|
||
|
if (atomic_test_and_clear_bit(&srv->flags, FLAG_ON_PENDING)) {
|
||
|
turn_on(srv, &transition, true);
|
||
|
} else if (atomic_test_and_clear_bit(&srv->flags, FLAG_OFF_PENDING)) {
|
||
|
turn_off(srv, &transition, true);
|
||
|
} else if (atomic_test_and_clear_bit(&srv->flags, FLAG_OCC_PENDING) &&
|
||
|
(srv->state == LIGHT_CTRL_STATE_ON ||
|
||
|
srv->state == LIGHT_CTRL_STATE_PROLONG ||
|
||
|
(/* Fade Standby Auto */
|
||
|
srv->state == LIGHT_CTRL_STATE_STANDBY &&
|
||
|
!atomic_test_bit(&srv->flags, FLAG_ON) &&
|
||
|
atomic_test_bit(&srv->flags, FLAG_TRANSITION)) ||
|
||
|
atomic_test_bit(&srv->flags, FLAG_OCC_MODE))) {
|
||
|
turn_on(srv, NULL, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if CONFIG_BT_SETTINGS
|
||
|
static void store_cfg_data(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
if (atomic_test_and_clear_bit(&srv->flags, FLAG_STORE_CFG)) {
|
||
|
struct setup_srv_storage_data data = {
|
||
|
.cfg = srv->cfg,
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
.reg_cfg = srv->reg->cfg,
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
(void)bt_mesh_model_data_store(srv->setup_srv, false, NULL,
|
||
|
&data, sizeof(data));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void store_state_data(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
if (atomic_test_and_clear_bit(&srv->flags, FLAG_STORE_STATE)) {
|
||
|
atomic_tt data = 0;
|
||
|
|
||
|
atomic_set_bit_to(&data, STORED_FLAG_ENABLED, is_enabled(srv));
|
||
|
atomic_set_bit_to(&data, STORED_FLAG_ON,
|
||
|
atomic_test_bit(&srv->flags, FLAG_ON));
|
||
|
atomic_set_bit_to(&data, STORED_FLAG_OCC_MODE,
|
||
|
atomic_test_bit(&srv->flags, FLAG_OCC_MODE));
|
||
|
|
||
|
(void)bt_mesh_model_data_store(srv->model, false, NULL, &data,
|
||
|
sizeof(data));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void ligth_ctrl_srv_pending_store(struct bt_mesh_model *model)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
|
||
|
log_debug("Store Timeout");
|
||
|
#if 0 //not adapter.
|
||
|
store_cfg_data(srv);
|
||
|
store_state_data(srv);
|
||
|
#endif
|
||
|
}
|
||
|
#endif
|
||
|
/*******************************************************************************
|
||
|
* Handlers
|
||
|
******************************************************************************/
|
||
|
|
||
|
static void mode_rsp(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
struct bt_mesh_msg_ctx *ctx)
|
||
|
{
|
||
|
BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_LIGHT_CTRL_OP_MODE_STATUS, 1);
|
||
|
bt_mesh_model_msg_init(&rsp, BT_MESH_LIGHT_CTRL_OP_MODE_STATUS);
|
||
|
net_buf_simple_add_u8(&rsp, is_enabled(srv));
|
||
|
|
||
|
bt_mesh_msg_send(srv->model, ctx, &rsp);
|
||
|
}
|
||
|
|
||
|
static int handle_mode_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
|
||
|
struct net_buf_simple *buf)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
|
||
|
mode_rsp(srv, ctx);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int mode_set(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
struct net_buf_simple *buf)
|
||
|
{
|
||
|
uint8_t mode = net_buf_simple_pull_u8(buf);
|
||
|
|
||
|
if (mode > 1) {
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
log_debug("Set Mode: %s", mode ? "enable" : "disable");
|
||
|
|
||
|
if (mode) {
|
||
|
bt_mesh_light_ctrl_srv_enable(srv);
|
||
|
} else {
|
||
|
bt_mesh_light_ctrl_srv_disable(srv);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int handle_mode_set(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
|
||
|
struct net_buf_simple *buf)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
int err;
|
||
|
|
||
|
err = mode_set(srv, buf);
|
||
|
if (err) {
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
mode_rsp(srv, ctx);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int handle_mode_set_unack(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
|
||
|
struct net_buf_simple *buf)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
|
||
|
return mode_set(srv, buf);
|
||
|
}
|
||
|
|
||
|
static void om_rsp(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
struct bt_mesh_msg_ctx *ctx)
|
||
|
{
|
||
|
BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_LIGHT_CTRL_OP_OM_STATUS, 1);
|
||
|
bt_mesh_model_msg_init(&rsp, BT_MESH_LIGHT_CTRL_OP_OM_STATUS);
|
||
|
net_buf_simple_add_u8(&rsp,
|
||
|
atomic_test_bit(&srv->flags, FLAG_OCC_MODE));
|
||
|
|
||
|
bt_mesh_msg_send(srv->model, ctx, &rsp);
|
||
|
}
|
||
|
|
||
|
static int handle_om_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
|
||
|
struct net_buf_simple *buf)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
|
||
|
om_rsp(srv, ctx);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int om_set(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
struct net_buf_simple *buf)
|
||
|
{
|
||
|
uint8_t mode = net_buf_simple_pull_u8(buf);
|
||
|
|
||
|
if (mode > 1) {
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
log_debug("Set OM: %s", mode ? "on" : "off");
|
||
|
|
||
|
atomic_set_bit_to(&srv->flags, FLAG_OCC_MODE, mode);
|
||
|
store(srv, FLAG_STORE_STATE);
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_BT_MESH_SCENE_SRV)) {
|
||
|
bt_mesh_scene_invalidate(srv->model);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int handle_om_set(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
|
||
|
struct net_buf_simple *buf)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
int err;
|
||
|
|
||
|
err = om_set(srv, buf);
|
||
|
if (err) {
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
om_rsp(srv, ctx);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int handle_om_set_unack(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
|
||
|
struct net_buf_simple *buf)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
|
||
|
return om_set(srv, buf);
|
||
|
}
|
||
|
|
||
|
static int handle_light_onoff_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
|
||
|
struct net_buf_simple *buf)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
|
||
|
log_debug("Get Light OnOff");
|
||
|
|
||
|
light_onoff_status_send(srv, ctx, srv->state);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static bool transition_get(struct bt_mesh_model *model,
|
||
|
struct bt_mesh_model_transition *transition,
|
||
|
struct net_buf_simple *buf)
|
||
|
{
|
||
|
/* Light LC Server uses state machine values instead of DTT if Transition Time field is not
|
||
|
* present in the message.
|
||
|
*/
|
||
|
if (buf->len == 2) {
|
||
|
model_transition_buf_pull(buf, transition);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static int light_onoff_set(struct bt_mesh_light_ctrl_srv *srv, struct bt_mesh_msg_ctx *ctx,
|
||
|
struct net_buf_simple *buf, bool ack)
|
||
|
{
|
||
|
uint8_t onoff = net_buf_simple_pull_u8(buf);
|
||
|
|
||
|
if (onoff > 1) {
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
uint8_t tid = net_buf_simple_pull_u8(buf);
|
||
|
|
||
|
struct bt_mesh_model_transition transition;
|
||
|
bool has_trans = transition_get(srv->model, &transition, buf);
|
||
|
|
||
|
enum bt_mesh_light_ctrl_srv_state prev_state = srv->state;
|
||
|
|
||
|
if (!tid_check_and_update(&srv->tid, tid, ctx)) {
|
||
|
log_debug("Set Light OnOff: %s (%u + %u)", onoff ? "on" : "off",
|
||
|
has_trans ? transition.time : 0,
|
||
|
has_trans ? transition.delay : 0);
|
||
|
|
||
|
if (has_trans && transition.delay > 0) {
|
||
|
delayed_set(srv, &transition, onoff);
|
||
|
} else if (onoff) {
|
||
|
turn_on(srv, has_trans ? &transition : NULL, true);
|
||
|
} else {
|
||
|
turn_off(srv, has_trans ? &transition : NULL, true);
|
||
|
}
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_BT_MESH_SCENE_SRV)) {
|
||
|
bt_mesh_scene_invalidate(srv->model);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (ack) {
|
||
|
light_onoff_status_send(srv, ctx, prev_state);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int handle_light_onoff_set(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
|
||
|
struct net_buf_simple *buf)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
|
||
|
return light_onoff_set(srv, ctx, buf, true);
|
||
|
}
|
||
|
|
||
|
static int handle_light_onoff_set_unack(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
|
||
|
struct net_buf_simple *buf)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
|
||
|
return light_onoff_set(srv, ctx, buf, false);
|
||
|
}
|
||
|
|
||
|
#if (CONFIG_BT_MESH_SENSOR_SRV || CONFIG_BT_MESH_SENSOR_CLI)
|
||
|
static int handle_sensor_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
|
||
|
struct net_buf_simple *buf)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
int err;
|
||
|
|
||
|
if (!is_enabled(srv)) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
while (buf->len >= 3) {
|
||
|
uint8_t len;
|
||
|
uint16_t id;
|
||
|
struct sensor_value value;
|
||
|
|
||
|
sensor_status_id_decode(buf, &len, &id);
|
||
|
|
||
|
const struct bt_mesh_sensor_type *type;
|
||
|
|
||
|
switch (id) {
|
||
|
case BT_MESH_PROP_ID_MOTION_SENSED:
|
||
|
type = &bt_mesh_sensor_motion_sensed;
|
||
|
break;
|
||
|
|
||
|
case BT_MESH_PROP_ID_PEOPLE_COUNT:
|
||
|
type = &bt_mesh_sensor_people_count;
|
||
|
break;
|
||
|
|
||
|
case BT_MESH_PROP_ID_PRESENCE_DETECTED:
|
||
|
type = &bt_mesh_sensor_presence_detected;
|
||
|
break;
|
||
|
|
||
|
case BT_MESH_PROP_ID_TIME_SINCE_MOTION_SENSED:
|
||
|
type = &bt_mesh_sensor_time_since_motion_sensed;
|
||
|
break;
|
||
|
|
||
|
case BT_MESH_PROP_ID_PRESENT_AMB_LIGHT_LEVEL:
|
||
|
type = &bt_mesh_sensor_present_amb_light_level;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
type = NULL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (!type) {
|
||
|
net_buf_simple_pull(buf, len);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
err = sensor_value_decode(buf, type, &value);
|
||
|
if (err) {
|
||
|
return -ENOENT;
|
||
|
}
|
||
|
|
||
|
log_debug("Sensor 0x%04x: %s", id, bt_mesh_sensor_ch_str(&value));
|
||
|
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
if (id == BT_MESH_PROP_ID_PRESENT_AMB_LIGHT_LEVEL && srv->reg) {
|
||
|
srv->reg->measured = sensor_to_float(&value);
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_AMB_LIGHT_LEVEL_TIMEOUT
|
||
|
srv->amb_light_level_timestamp = k_uptime_get();
|
||
|
#endif
|
||
|
if (!atomic_test_and_set_bit(&srv->flags, FLAG_AMBIENT_LUXLEVEL_SET)) {
|
||
|
reg_start(srv);
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Occupancy sensor */
|
||
|
|
||
|
if ((srv->state == LIGHT_CTRL_STATE_STANDBY &&
|
||
|
/* According to the Mesh Model Specification section
|
||
|
* 6.2.5.6: When in the specifications STANDBY state,
|
||
|
* and the Auto Occupancy condition is false, the
|
||
|
* Occupancy On event should not be processed.
|
||
|
*/
|
||
|
((!atomic_test_bit(&srv->flags, FLAG_OCC_MODE) &&
|
||
|
!atomic_test_bit(&srv->flags, FLAG_TRANSITION) &&
|
||
|
!atomic_test_bit(&srv->flags, FLAG_MANUAL)) ||
|
||
|
/* According to the Mesh Model Specification section
|
||
|
* 6.2.5.12: When in the specifications FADE STANDBY
|
||
|
* MANUAL state, the Occupancy On event should not be
|
||
|
* processed.
|
||
|
*/
|
||
|
(atomic_test_bit(&srv->flags, FLAG_TRANSITION) &&
|
||
|
atomic_test_bit(&srv->flags, FLAG_MANUAL)))) ||
|
||
|
/* According to the Mesh Model Specification section
|
||
|
* 6.2.5.7: When in the specifications FADE ON state, the
|
||
|
* Occupancy On event should not be processed.
|
||
|
*/
|
||
|
(srv->state == LIGHT_CTRL_STATE_ON &&
|
||
|
atomic_test_bit(&srv->flags, FLAG_TRANSITION))) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* Decode entire value to float, to get actual sensor value. */
|
||
|
float val = sensor_to_float(&value);
|
||
|
|
||
|
log_debug("Checking sensor val");
|
||
|
if (id == BT_MESH_PROP_ID_TIME_SINCE_MOTION_SENSED) {
|
||
|
delayed_occupancy(srv, value.val1);
|
||
|
} else if (val > 0) {
|
||
|
delayed_occupancy(srv, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
const struct bt_mesh_model_op _bt_mesh_light_ctrl_srv_op[] = {
|
||
|
{
|
||
|
BT_MESH_LIGHT_CTRL_OP_MODE_GET,
|
||
|
BT_MESH_LEN_EXACT(BT_MESH_LIGHT_CTRL_MSG_LEN_MODE_GET),
|
||
|
handle_mode_get,
|
||
|
},
|
||
|
{
|
||
|
BT_MESH_LIGHT_CTRL_OP_MODE_SET,
|
||
|
BT_MESH_LEN_EXACT(BT_MESH_LIGHT_CTRL_MSG_LEN_MODE_SET),
|
||
|
handle_mode_set,
|
||
|
},
|
||
|
{
|
||
|
BT_MESH_LIGHT_CTRL_OP_MODE_SET_UNACK,
|
||
|
BT_MESH_LEN_EXACT(BT_MESH_LIGHT_CTRL_MSG_LEN_MODE_SET),
|
||
|
handle_mode_set_unack,
|
||
|
},
|
||
|
{
|
||
|
BT_MESH_LIGHT_CTRL_OP_OM_GET,
|
||
|
BT_MESH_LEN_EXACT(BT_MESH_LIGHT_CTRL_MSG_LEN_OM_GET),
|
||
|
handle_om_get,
|
||
|
},
|
||
|
{
|
||
|
BT_MESH_LIGHT_CTRL_OP_OM_SET,
|
||
|
BT_MESH_LEN_EXACT(BT_MESH_LIGHT_CTRL_MSG_LEN_OM_SET),
|
||
|
handle_om_set,
|
||
|
},
|
||
|
{
|
||
|
BT_MESH_LIGHT_CTRL_OP_OM_SET_UNACK,
|
||
|
BT_MESH_LEN_EXACT(BT_MESH_LIGHT_CTRL_MSG_LEN_OM_SET),
|
||
|
handle_om_set_unack,
|
||
|
},
|
||
|
{
|
||
|
BT_MESH_LIGHT_CTRL_OP_LIGHT_ONOFF_GET,
|
||
|
BT_MESH_LEN_EXACT(BT_MESH_LIGHT_CTRL_MSG_LEN_LIGHT_ONOFF_GET),
|
||
|
handle_light_onoff_get,
|
||
|
},
|
||
|
{
|
||
|
BT_MESH_LIGHT_CTRL_OP_LIGHT_ONOFF_SET,
|
||
|
BT_MESH_LEN_MIN(BT_MESH_LIGHT_CTRL_MSG_MINLEN_LIGHT_ONOFF_SET),
|
||
|
handle_light_onoff_set,
|
||
|
},
|
||
|
{
|
||
|
BT_MESH_LIGHT_CTRL_OP_LIGHT_ONOFF_SET_UNACK,
|
||
|
BT_MESH_LEN_MIN(BT_MESH_LIGHT_CTRL_MSG_MINLEN_LIGHT_ONOFF_SET),
|
||
|
handle_light_onoff_set_unack,
|
||
|
},
|
||
|
#if (CONFIG_BT_MESH_SENSOR_SRV || CONFIG_BT_MESH_SENSOR_CLI)
|
||
|
{
|
||
|
BT_MESH_SENSOR_OP_STATUS,
|
||
|
BT_MESH_LEN_MIN(BT_MESH_SENSOR_MSG_MINLEN_STATUS),
|
||
|
handle_sensor_status,
|
||
|
},
|
||
|
#endif
|
||
|
BT_MESH_MODEL_OP_END,
|
||
|
};
|
||
|
|
||
|
#if (CONFIG_BT_MESH_SENSOR_SRV || CONFIG_BT_MESH_SENSOR_CLI)
|
||
|
static void to_prop_time(uint32_t time, struct sensor_value *prop)
|
||
|
{
|
||
|
prop->val1 = time / MSEC_PER_SEC;
|
||
|
prop->val2 = (time % MSEC_PER_SEC) * 1000;
|
||
|
}
|
||
|
|
||
|
static uint32_t from_prop_time(const struct sensor_value *prop)
|
||
|
{
|
||
|
return prop->val1 * MSEC_PER_SEC + prop->val2 / 1000;
|
||
|
}
|
||
|
|
||
|
static int prop_get(struct net_buf_simple *buf,
|
||
|
const struct bt_mesh_light_ctrl_srv *srv, uint16_t id)
|
||
|
{
|
||
|
struct sensor_value val = {0};
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
if (srv->reg) {
|
||
|
switch (id) {
|
||
|
/* Regulator coefficients are raw IEEE-754 floats, push them straight
|
||
|
* to the buffer instead of using sensor to encode them:
|
||
|
*/
|
||
|
case BT_MESH_LIGHT_CTRL_COEFF_KID:
|
||
|
net_buf_simple_add_mem(buf, &srv->reg->cfg.ki.down, sizeof(float));
|
||
|
return 0;
|
||
|
case BT_MESH_LIGHT_CTRL_COEFF_KIU:
|
||
|
net_buf_simple_add_mem(buf, &srv->reg->cfg.ki.up, sizeof(float));
|
||
|
return 0;
|
||
|
case BT_MESH_LIGHT_CTRL_COEFF_KPD:
|
||
|
net_buf_simple_add_mem(buf, &srv->reg->cfg.kp.down, sizeof(float));
|
||
|
return 0;
|
||
|
case BT_MESH_LIGHT_CTRL_COEFF_KPU:
|
||
|
net_buf_simple_add_mem(buf, &srv->reg->cfg.kp.up, sizeof(float));
|
||
|
return 0;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_REG_ACCURACY:
|
||
|
val.val1 = srv->reg->cfg.accuracy;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
switch (id) {
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_ILLUMINANCE_ON:
|
||
|
val = srv->cfg.lux[LIGHT_CTRL_STATE_ON];
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_ILLUMINANCE_PROLONG:
|
||
|
val = srv->cfg.lux[LIGHT_CTRL_STATE_PROLONG];
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_ILLUMINANCE_STANDBY:
|
||
|
val = srv->cfg.lux[LIGHT_CTRL_STATE_STANDBY];
|
||
|
break;
|
||
|
#else
|
||
|
switch (id) {
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_ILLUMINANCE_ON:
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_ILLUMINANCE_PROLONG:
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_ILLUMINANCE_STANDBY:
|
||
|
#endif
|
||
|
case BT_MESH_LIGHT_CTRL_COEFF_KID:
|
||
|
case BT_MESH_LIGHT_CTRL_COEFF_KIU:
|
||
|
case BT_MESH_LIGHT_CTRL_COEFF_KPD:
|
||
|
case BT_MESH_LIGHT_CTRL_COEFF_KPU:
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_REG_ACCURACY:
|
||
|
break; /* Prevent returning -ENOENT */
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_LIGHTNESS_ON:
|
||
|
val.val1 = to_actual(srv->cfg.light[LIGHT_CTRL_STATE_ON]);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_LIGHTNESS_PROLONG:
|
||
|
val.val1 = to_actual(srv->cfg.light[LIGHT_CTRL_STATE_PROLONG]);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_LIGHTNESS_STANDBY:
|
||
|
val.val1 = to_actual(srv->cfg.light[LIGHT_CTRL_STATE_STANDBY]);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_TIME_FADE_PROLONG:
|
||
|
to_prop_time(srv->cfg.fade_prolong, &val);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_TIME_FADE_ON:
|
||
|
to_prop_time(srv->cfg.fade_on, &val);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_TIME_FADE_STANDBY_AUTO:
|
||
|
to_prop_time(srv->cfg.fade_standby_auto, &val);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_TIME_FADE_STANDBY_MANUAL:
|
||
|
to_prop_time(srv->cfg.fade_standby_manual, &val);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_TIME_OCCUPANCY_DELAY:
|
||
|
to_prop_time(srv->cfg.occupancy_delay, &val);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_TIME_PROLONG:
|
||
|
to_prop_time(srv->cfg.prolong, &val);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_TIME_ON:
|
||
|
to_prop_time(srv->cfg.on, &val);
|
||
|
break;
|
||
|
default:
|
||
|
return -ENOENT;
|
||
|
}
|
||
|
|
||
|
return prop_encode(buf, id, &val);
|
||
|
}
|
||
|
|
||
|
static int prop_set(struct net_buf_simple *buf,
|
||
|
struct bt_mesh_light_ctrl_srv *srv, uint16_t id)
|
||
|
{
|
||
|
struct sensor_value val;
|
||
|
int err;
|
||
|
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
/* Regulator coefficients are raw IEEE-754 floats, pull them straight
|
||
|
* from the buffer instead of using sensor to decode them:
|
||
|
*/
|
||
|
if (srv->reg) {
|
||
|
switch (id) {
|
||
|
case BT_MESH_LIGHT_CTRL_COEFF_KID:
|
||
|
memcpy(&srv->reg->cfg.ki.down,
|
||
|
net_buf_simple_pull_mem(buf, sizeof(float)),
|
||
|
sizeof(float));
|
||
|
return 0;
|
||
|
case BT_MESH_LIGHT_CTRL_COEFF_KIU:
|
||
|
memcpy(&srv->reg->cfg.ki.up,
|
||
|
net_buf_simple_pull_mem(buf, sizeof(float)),
|
||
|
sizeof(float));
|
||
|
return 0;
|
||
|
case BT_MESH_LIGHT_CTRL_COEFF_KPD:
|
||
|
memcpy(&srv->reg->cfg.kp.down,
|
||
|
net_buf_simple_pull_mem(buf, sizeof(float)),
|
||
|
sizeof(float));
|
||
|
return 0;
|
||
|
case BT_MESH_LIGHT_CTRL_COEFF_KPU:
|
||
|
memcpy(&srv->reg->cfg.kp.up,
|
||
|
net_buf_simple_pull_mem(buf, sizeof(float)),
|
||
|
sizeof(float));
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
err = prop_decode(buf, id, &val);
|
||
|
if (err) {
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
if (buf->len > 0) {
|
||
|
log_error("Invalid message size");
|
||
|
return -EMSGSIZE;
|
||
|
}
|
||
|
|
||
|
log_debug("Set Prop: 0x%04x: %s", id, bt_mesh_sensor_ch_str(&val));
|
||
|
|
||
|
switch (id) {
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_REG_ACCURACY:
|
||
|
if (srv->reg) {
|
||
|
srv->reg->cfg.accuracy = val.val1;
|
||
|
}
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_ILLUMINANCE_ON:
|
||
|
srv->cfg.lux[LIGHT_CTRL_STATE_ON] = val;
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_ILLUMINANCE_PROLONG:
|
||
|
srv->cfg.lux[LIGHT_CTRL_STATE_PROLONG] = val;
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_ILLUMINANCE_STANDBY:
|
||
|
srv->cfg.lux[LIGHT_CTRL_STATE_STANDBY] = val;
|
||
|
break;
|
||
|
#else
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_ILLUMINANCE_ON:
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_ILLUMINANCE_PROLONG:
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_ILLUMINANCE_STANDBY:
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_REG_ACCURACY:
|
||
|
#endif
|
||
|
case BT_MESH_LIGHT_CTRL_COEFF_KID:
|
||
|
case BT_MESH_LIGHT_CTRL_COEFF_KIU:
|
||
|
case BT_MESH_LIGHT_CTRL_COEFF_KPD:
|
||
|
case BT_MESH_LIGHT_CTRL_COEFF_KPU:
|
||
|
break; /* Prevent returning -ENOENT */
|
||
|
/* Properties are always set in light actual representation: */
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_LIGHTNESS_ON:
|
||
|
srv->cfg.light[LIGHT_CTRL_STATE_ON] = from_actual(val.val1);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_LIGHTNESS_PROLONG:
|
||
|
srv->cfg.light[LIGHT_CTRL_STATE_PROLONG] = from_actual(val.val1);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_LIGHTNESS_STANDBY:
|
||
|
srv->cfg.light[LIGHT_CTRL_STATE_STANDBY] = from_actual(val.val1);
|
||
|
|
||
|
/* If light is already in STANDBY, or transitioning to STANDBY,
|
||
|
* update the target level to new value.
|
||
|
*/
|
||
|
if (srv->state == LIGHT_CTRL_STATE_STANDBY) {
|
||
|
/* Check if transition is in progress */
|
||
|
if (atomic_test_bit(&srv->flags, FLAG_TRANSITION)) {
|
||
|
uint32_t rem_time = remaining_fade_time(srv);
|
||
|
|
||
|
transition_start(srv, LIGHT_CTRL_STATE_STANDBY,
|
||
|
rem_time);
|
||
|
} else {
|
||
|
transition_start(srv, LIGHT_CTRL_STATE_STANDBY,
|
||
|
0);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_TIME_FADE_PROLONG:
|
||
|
srv->cfg.fade_prolong = from_prop_time(&val);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_TIME_FADE_ON:
|
||
|
srv->cfg.fade_on = from_prop_time(&val);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_TIME_FADE_STANDBY_AUTO:
|
||
|
srv->cfg.fade_standby_auto = from_prop_time(&val);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_TIME_FADE_STANDBY_MANUAL:
|
||
|
srv->cfg.fade_standby_manual = from_prop_time(&val);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_TIME_OCCUPANCY_DELAY:
|
||
|
srv->cfg.occupancy_delay = from_prop_time(&val);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_TIME_PROLONG:
|
||
|
srv->cfg.prolong = from_prop_time(&val);
|
||
|
break;
|
||
|
case BT_MESH_LIGHT_CTRL_PROP_TIME_ON:
|
||
|
srv->cfg.on = from_prop_time(&val);
|
||
|
break;
|
||
|
default:
|
||
|
return -ENOENT;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int prop_tx(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
struct bt_mesh_msg_ctx *ctx, uint16_t id)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
BT_MESH_MODEL_BUF_DEFINE(
|
||
|
buf, BT_MESH_LIGHT_CTRL_OP_PROP_STATUS,
|
||
|
2 + CONFIG_BT_MESH_SENSOR_CHANNEL_ENCODED_SIZE_MAX);
|
||
|
bt_mesh_model_msg_init(&buf, BT_MESH_LIGHT_CTRL_OP_PROP_STATUS);
|
||
|
net_buf_simple_add_le16(&buf, id);
|
||
|
|
||
|
err = prop_get(&buf, srv, id);
|
||
|
if (err) {
|
||
|
return -ENOENT;
|
||
|
}
|
||
|
|
||
|
bt_mesh_msg_send(srv->setup_srv, ctx, &buf);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int handle_prop_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
|
||
|
struct net_buf_simple *buf)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
uint16_t id = net_buf_simple_pull_le16(buf);
|
||
|
|
||
|
return prop_tx(srv, ctx, id);
|
||
|
}
|
||
|
|
||
|
static int handle_prop_set(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
|
||
|
struct net_buf_simple *buf)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
uint16_t id = net_buf_simple_pull_le16(buf);
|
||
|
int err;
|
||
|
|
||
|
err = prop_set(buf, srv, id);
|
||
|
if (err) {
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
(void)prop_tx(srv, ctx, id);
|
||
|
(void)prop_tx(srv, NULL, id);
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_BT_MESH_SCENE_SRV)) {
|
||
|
bt_mesh_scene_invalidate(srv->model);
|
||
|
}
|
||
|
|
||
|
store(srv, FLAG_STORE_CFG);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int handle_prop_set_unack(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
|
||
|
struct net_buf_simple *buf)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
int err;
|
||
|
|
||
|
uint16_t id = net_buf_simple_pull_le16(buf);
|
||
|
|
||
|
err = prop_set(buf, srv, id);
|
||
|
if (err) {
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
(void)prop_tx(srv, NULL, id);
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_BT_MESH_SCENE_SRV)) {
|
||
|
bt_mesh_scene_invalidate(srv->model);
|
||
|
}
|
||
|
|
||
|
store(srv, FLAG_STORE_CFG);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
const struct bt_mesh_model_op _bt_mesh_light_ctrl_setup_srv_op[] = {
|
||
|
{
|
||
|
BT_MESH_LIGHT_CTRL_OP_PROP_GET,
|
||
|
BT_MESH_LEN_EXACT(BT_MESH_LIGHT_CTRL_MSG_LEN_PROP_GET),
|
||
|
handle_prop_get,
|
||
|
},
|
||
|
{
|
||
|
BT_MESH_LIGHT_CTRL_OP_PROP_SET,
|
||
|
BT_MESH_LEN_MIN(BT_MESH_LIGHT_CTRL_MSG_MINLEN_PROP_SET),
|
||
|
handle_prop_set,
|
||
|
},
|
||
|
{
|
||
|
BT_MESH_LIGHT_CTRL_OP_PROP_SET_UNACK,
|
||
|
BT_MESH_LEN_MIN(BT_MESH_LIGHT_CTRL_MSG_MINLEN_PROP_SET),
|
||
|
handle_prop_set_unack,
|
||
|
},
|
||
|
BT_MESH_MODEL_OP_END,
|
||
|
};
|
||
|
#endif
|
||
|
|
||
|
/*******************************************************************************
|
||
|
* Callbacks
|
||
|
******************************************************************************/
|
||
|
|
||
|
static void onoff_set(struct bt_mesh_onoff_srv *onoff,
|
||
|
struct bt_mesh_msg_ctx *ctx,
|
||
|
const struct bt_mesh_onoff_set *set,
|
||
|
struct bt_mesh_onoff_status *rsp)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv =
|
||
|
CONTAINER_OF(onoff, struct bt_mesh_light_ctrl_srv, onoff);
|
||
|
enum bt_mesh_light_ctrl_srv_state prev_state = srv->state;
|
||
|
|
||
|
if (set->transition && set->transition->delay > 0) {
|
||
|
delayed_set(srv, set->transition, set->on_off);
|
||
|
} else if (set->on_off) {
|
||
|
turn_on(srv, set->transition, false);
|
||
|
} else {
|
||
|
turn_off(srv, set->transition, false);
|
||
|
}
|
||
|
|
||
|
if (!rsp) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
onoff_encode(srv, prev_state, rsp);
|
||
|
}
|
||
|
|
||
|
static void onoff_get(struct bt_mesh_onoff_srv *onoff,
|
||
|
struct bt_mesh_msg_ctx *ctx,
|
||
|
struct bt_mesh_onoff_status *rsp)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv =
|
||
|
CONTAINER_OF(onoff, struct bt_mesh_light_ctrl_srv, onoff);
|
||
|
|
||
|
onoff_encode(srv, bt_mesh_light_ctrl_srv_is_on(srv), rsp);
|
||
|
}
|
||
|
|
||
|
const struct bt_mesh_onoff_srv_handlers _bt_mesh_light_ctrl_srv_onoff = {
|
||
|
.set = onoff_set,
|
||
|
.get = onoff_get,
|
||
|
};
|
||
|
|
||
|
struct __attribute__((__packed__)) scene_data {
|
||
|
uint8_t enabled : 1,
|
||
|
occ : 1,
|
||
|
light : 1;
|
||
|
struct bt_mesh_light_ctrl_srv_cfg cfg;
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
struct bt_mesh_light_ctrl_reg_cfg reg;
|
||
|
#endif
|
||
|
uint16_t lightness;
|
||
|
};
|
||
|
|
||
|
static ssize_t scene_store(struct bt_mesh_model *model, uint8_t data[])
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
struct bt_mesh_lightness_status light = {0};
|
||
|
struct scene_data *scene = (struct scene_data *)&data[0];
|
||
|
|
||
|
scene->enabled = is_enabled(srv);
|
||
|
scene->occ = atomic_test_bit(&srv->flags, FLAG_OCC_MODE);
|
||
|
scene->light = atomic_test_bit(&srv->flags, FLAG_ON);
|
||
|
scene->cfg = srv->cfg;
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
if (srv->reg) {
|
||
|
scene->reg = srv->reg->cfg;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
srv->lightness->handlers->light_get(srv->lightness, NULL, &light);
|
||
|
scene->lightness = to_actual(light.remaining_time ? light.target : light.current);
|
||
|
|
||
|
return sizeof(struct scene_data);
|
||
|
}
|
||
|
|
||
|
static void scene_recall(struct bt_mesh_model *model, const uint8_t data[],
|
||
|
size_t len,
|
||
|
struct bt_mesh_model_transition *transition)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
struct scene_data *scene = (struct scene_data *)&data[0];
|
||
|
|
||
|
atomic_set_bit_to(&srv->flags, FLAG_OCC_MODE, scene->occ);
|
||
|
srv->cfg = scene->cfg;
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
if (srv->reg) {
|
||
|
srv->reg->cfg = scene->reg;
|
||
|
}
|
||
|
#endif
|
||
|
if (scene->enabled) {
|
||
|
if (!is_enabled(srv)) {
|
||
|
ctrl_enable(srv);
|
||
|
}
|
||
|
|
||
|
if (!!scene->light && !atomic_test_bit(&srv->flags, FLAG_ON)) {
|
||
|
turn_on(srv, transition, true);
|
||
|
} else if (atomic_test_bit(&srv->flags, FLAG_ON)) {
|
||
|
transition_start(srv, LIGHT_CTRL_STATE_STANDBY, 0);
|
||
|
light_onoff_pub(srv, srv->state, true);
|
||
|
}
|
||
|
} else {
|
||
|
struct bt_mesh_lightness_status status;
|
||
|
struct bt_mesh_lightness_set set = {
|
||
|
.lvl = from_actual(scene->lightness),
|
||
|
.transition = transition,
|
||
|
};
|
||
|
|
||
|
ctrl_disable(srv);
|
||
|
if (atomic_test_bit(&srv->flags, FLAG_RESUME_TIMER)) {
|
||
|
schedule_resume_timer(srv);
|
||
|
}
|
||
|
lightness_srv_change_lvl(srv->lightness, NULL, &set, &status, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* MeshMDL1.0.1, section 5.1.3.1.1:
|
||
|
* If a model is extending another model, the extending model shall determine
|
||
|
* the Stored with Scene behavior of that model.
|
||
|
*
|
||
|
* Use Setup Model to handle Scene Store/Recall as it is not extended
|
||
|
* by other models.
|
||
|
*/
|
||
|
// BT_MESH_SCENE_ENTRY_SIG(light_ctrl) = {
|
||
|
// .id.sig = BT_MESH_MODEL_ID_LIGHT_LC_SETUPSRV,
|
||
|
// .maxlen = sizeof(struct scene_data),
|
||
|
// .store = scene_store,
|
||
|
// .recall = scene_recall,
|
||
|
// };
|
||
|
const struct bt_mesh_scene_entry bt_mesh_scene_entry_sig_light_ctrl = {
|
||
|
.id.sig = BT_MESH_MODEL_ID_LIGHT_LC_SRV,
|
||
|
.maxlen = sizeof(struct scene_data),
|
||
|
.store = scene_store,
|
||
|
.recall = scene_recall,
|
||
|
};
|
||
|
|
||
|
static int update_handler(struct bt_mesh_model *model)
|
||
|
{
|
||
|
log_debug("Update Handler");
|
||
|
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
|
||
|
light_onoff_encode(srv, srv->pub.msg, srv->state);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int light_ctrl_srv_init(struct bt_mesh_model *model)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
int err;
|
||
|
|
||
|
srv->model = model;
|
||
|
|
||
|
if (srv->lightness->lightness_model == NULL ||
|
||
|
srv->lightness->lightness_model->rt->elem_idx >= model->rt->elem_idx) {
|
||
|
log_error("Lightness: Invalid element index");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_BT_MESH_LIGHT_CTRL_SRV_OCCUPANCY_MODE)) {
|
||
|
atomic_set_bit(&srv->flags, FLAG_OCC_MODE);
|
||
|
}
|
||
|
|
||
|
k_work_init_delayable(&srv->timer, timeout);
|
||
|
k_work_init_delayable(&srv->action_delay, delayed_action_timeout);
|
||
|
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
if (srv->reg) {
|
||
|
struct bt_mesh_light_ctrl_reg_cfg reg_cfg = BT_MESH_LIGHT_CTRL_SRV_REG_CFG_INIT;
|
||
|
|
||
|
srv->reg->updated = reg_updated;
|
||
|
srv->reg->user_data = srv;
|
||
|
srv->reg->cfg = reg_cfg;
|
||
|
if (srv->reg->init) {
|
||
|
srv->reg->init(srv->reg);
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
srv->resume = CONFIG_BT_MESH_LIGHT_CTRL_SRV_RESUME_DELAY;
|
||
|
|
||
|
srv->pub.msg = &srv->pub_buf;
|
||
|
srv->pub.update = update_handler;
|
||
|
net_buf_simple_init_with_data(&srv->pub_buf, srv->pub_data,
|
||
|
sizeof(srv->pub_data));
|
||
|
#if CONFIG_BT_MESH_MODEL_EXTENSIONS
|
||
|
err = bt_mesh_model_extend(model, srv->lightness->lightness_model);
|
||
|
if (err) {
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
err = bt_mesh_model_extend(model, srv->onoff.model);
|
||
|
if (err) {
|
||
|
return err;
|
||
|
}
|
||
|
#endif
|
||
|
atomic_set_bit(&srv->lightness->flags, LIGHTNESS_SRV_FLAG_EXTENDED_BY_LIGHT_CTRL);
|
||
|
|
||
|
atomic_set_bit(&srv->onoff.flags, GEN_ONOFF_SRV_NO_DTT);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int light_ctrl_srv_settings_set(struct bt_mesh_model *model,
|
||
|
const char *name, size_t len_rd,
|
||
|
settings_read_cb read_cb, void *cb_arg)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
atomic_tt data;
|
||
|
ssize_t result;
|
||
|
|
||
|
if (name) {
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if (!read_cb) {
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
result = read_cb(cb_arg, &data, sizeof(data));
|
||
|
if (result <= 0) {
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
if (result < sizeof(data)) {
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (atomic_test_bit(&data, STORED_FLAG_ON)) {
|
||
|
atomic_set_bit(&srv->flags, FLAG_ON);
|
||
|
}
|
||
|
|
||
|
if (atomic_test_bit(&data, STORED_FLAG_OCC_MODE)) {
|
||
|
atomic_set_bit(&srv->flags, FLAG_OCC_MODE);
|
||
|
}
|
||
|
|
||
|
if (atomic_test_bit(&data, STORED_FLAG_ENABLED)) {
|
||
|
srv->lightness->ctrl = srv;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int light_ctrl_srv_start(struct bt_mesh_model *model)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
|
||
|
atomic_set_bit(&srv->flags, FLAG_STARTED);
|
||
|
|
||
|
switch (srv->lightness->ponoff.on_power_up) {
|
||
|
case BT_MESH_ON_POWER_UP_OFF:
|
||
|
case BT_MESH_ON_POWER_UP_ON:
|
||
|
if (atomic_test_bit(&srv->flags,
|
||
|
FLAG_CTRL_SRV_MANUALLY_ENABLED)) {
|
||
|
ctrl_enable(srv);
|
||
|
} else {
|
||
|
lightness_on_power_up(srv->lightness);
|
||
|
ctrl_disable(srv);
|
||
|
schedule_resume_timer(srv);
|
||
|
}
|
||
|
|
||
|
/* PTS Corner case: If the device restarts while in the On state
|
||
|
* (with OnPowerUp != RESTORE), we'll end up here with lights
|
||
|
* off. If OnPowerUp is then changed to RESTORE, and the device
|
||
|
* restarts, we'll restore to On even though we were off in the
|
||
|
* previous power cycle, unless we store the Off state here.
|
||
|
*/
|
||
|
store(srv, FLAG_STORE_STATE);
|
||
|
break;
|
||
|
case BT_MESH_ON_POWER_UP_RESTORE:
|
||
|
if (is_enabled(srv)) {
|
||
|
reg_start(srv);
|
||
|
if (atomic_test_bit(&srv->flags, FLAG_ON)) {
|
||
|
turn_on(srv, NULL, true);
|
||
|
} else {
|
||
|
light_onoff_pub(srv, srv->state, true);
|
||
|
}
|
||
|
} else if (srv->lightness->transient.is_on) {
|
||
|
lightness_on_power_up(srv->lightness);
|
||
|
schedule_resume_timer(srv);
|
||
|
} else {
|
||
|
schedule_resume_timer(srv);
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void light_ctrl_srv_reset(struct bt_mesh_model *model)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
struct bt_mesh_light_ctrl_srv_cfg cfg = BT_MESH_LIGHT_CTRL_SRV_CFG_INIT;
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
struct bt_mesh_light_ctrl_reg_cfg reg_cfg = BT_MESH_LIGHT_CTRL_SRV_REG_CFG_INIT;
|
||
|
#endif
|
||
|
|
||
|
srv->cfg = cfg;
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
if (srv->reg) {
|
||
|
srv->reg->cfg = reg_cfg;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
ctrl_disable(srv);
|
||
|
net_buf_simple_reset(srv->pub.msg);
|
||
|
srv->resume = CONFIG_BT_MESH_LIGHT_CTRL_SRV_RESUME_DELAY;
|
||
|
|
||
|
if (IS_ENABLED(0)) {
|
||
|
(void)bt_mesh_model_data_store(srv->setup_srv, false, NULL,
|
||
|
NULL, 0);
|
||
|
(void)bt_mesh_model_data_store(srv->model, false, NULL,
|
||
|
NULL, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const struct bt_mesh_model_cb _bt_mesh_light_ctrl_srv_cb = {
|
||
|
.init = light_ctrl_srv_init,
|
||
|
.start = light_ctrl_srv_start,
|
||
|
.reset = light_ctrl_srv_reset,
|
||
|
#if CONFIG_BT_SETTINGS
|
||
|
.settings_set = light_ctrl_srv_settings_set,
|
||
|
.pending_store = ligth_ctrl_srv_pending_store,
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
static int lc_setup_srv_init(struct bt_mesh_model *model)
|
||
|
{
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
int err;
|
||
|
|
||
|
srv->setup_srv = model;
|
||
|
#if CONFIG_BT_MESH_MODEL_EXTENSIONS
|
||
|
err = bt_mesh_model_extend(srv->setup_srv, srv->model);
|
||
|
#else
|
||
|
err = 0;
|
||
|
#endif
|
||
|
if (err) {
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
#if (CONFIG_BT_MESH_COMP_PAGE_1)
|
||
|
err = bt_mesh_model_correspond(srv->setup_srv, srv->model);
|
||
|
if (err) {
|
||
|
return err;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
srv->setup_pub.msg = &srv->setup_pub_buf;
|
||
|
net_buf_simple_init_with_data(&srv->setup_pub_buf, srv->setup_pub_data,
|
||
|
sizeof(srv->setup_pub_data));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int lc_setup_srv_settings_set(struct bt_mesh_model *model,
|
||
|
const char *name, size_t len_rd,
|
||
|
settings_read_cb read_cb, void *cb_arg)
|
||
|
{
|
||
|
if (!read_cb) {
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
struct bt_mesh_light_ctrl_srv *srv = model->rt->user_data;
|
||
|
struct setup_srv_storage_data data;
|
||
|
ssize_t result;
|
||
|
|
||
|
if (name) {
|
||
|
return -ENOENT;
|
||
|
}
|
||
|
|
||
|
result = read_cb(cb_arg, &data, sizeof(data));
|
||
|
if (result <= 0) {
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
if (result < sizeof(data)) {
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
srv->cfg = data.cfg;
|
||
|
|
||
|
#if CONFIG_BT_MESH_LIGHT_CTRL_SRV_REG
|
||
|
if (srv->reg) {
|
||
|
srv->reg->cfg = data.reg_cfg;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
const struct bt_mesh_model_cb _bt_mesh_light_ctrl_setup_srv_cb = {
|
||
|
.init = lc_setup_srv_init,
|
||
|
#if CONFIG_BT_SETTINGS
|
||
|
.settings_set = lc_setup_srv_settings_set,
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
/*******************************************************************************
|
||
|
* Public API
|
||
|
******************************************************************************/
|
||
|
|
||
|
int bt_mesh_light_ctrl_srv_on(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
err = turn_on(srv, NULL, true);
|
||
|
if (!err && IS_ENABLED(CONFIG_BT_MESH_SCENE_SRV)) {
|
||
|
bt_mesh_scene_invalidate(srv->model);
|
||
|
}
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
int bt_mesh_light_ctrl_srv_off(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
err = turn_off(srv, NULL, true);
|
||
|
if (!err && IS_ENABLED(CONFIG_BT_MESH_SCENE_SRV)) {
|
||
|
bt_mesh_scene_invalidate(srv->model);
|
||
|
}
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
int bt_mesh_light_ctrl_srv_enable(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
atomic_set_bit(&srv->flags, FLAG_CTRL_SRV_MANUALLY_ENABLED);
|
||
|
if (is_enabled(srv)) {
|
||
|
return -EALREADY;
|
||
|
}
|
||
|
|
||
|
if (atomic_test_bit(&srv->flags, FLAG_STARTED)) {
|
||
|
ctrl_enable(srv);
|
||
|
store(srv, FLAG_STORE_STATE);
|
||
|
if (IS_ENABLED(CONFIG_BT_MESH_SCENE_SRV)) {
|
||
|
bt_mesh_scene_invalidate(srv->model);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int bt_mesh_light_ctrl_srv_disable(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
atomic_clear_bit(&srv->flags, FLAG_CTRL_SRV_MANUALLY_ENABLED);
|
||
|
|
||
|
if (!is_enabled(srv)) {
|
||
|
if (atomic_test_bit(&srv->flags, FLAG_RESUME_TIMER)) {
|
||
|
/* Restart resume timer even
|
||
|
* if the server has already been disabled:
|
||
|
*/
|
||
|
schedule_resume_timer(srv);
|
||
|
}
|
||
|
return -EALREADY;
|
||
|
}
|
||
|
|
||
|
ctrl_disable(srv);
|
||
|
schedule_resume_timer(srv);
|
||
|
store(srv, FLAG_STORE_STATE);
|
||
|
if (IS_ENABLED(CONFIG_BT_MESH_SCENE_SRV)) {
|
||
|
bt_mesh_scene_invalidate(srv->model);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
bool bt_mesh_light_ctrl_srv_is_on(struct bt_mesh_light_ctrl_srv *srv)
|
||
|
{
|
||
|
return is_enabled(srv) && state_is_on(srv, srv->state);
|
||
|
}
|
||
|
|
||
|
int bt_mesh_light_ctrl_srv_pub(struct bt_mesh_light_ctrl_srv *srv,
|
||
|
struct bt_mesh_msg_ctx *ctx)
|
||
|
{
|
||
|
return light_onoff_status_send(srv, ctx, srv->state);
|
||
|
}
|
||
|
|
||
|
#endif /* CONFIG_BT_MESH_LIGHT_CTRL_SRV */
|