AC63_BT_SDK/apps/mesh/MshMDL/lightness_srv.c
2025-02-18 15:40:42 +08:00

1042 lines
33 KiB
C

#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/gen_onoff_srv.h"
#include "api/light_ctrl_internal.h"
#include "model_types.h"
#include "model_utils.h"
#include "model_api.h"
#define LOG_TAG "[Light_Lightness]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
/* #define LOG_DUMP_ENABLE */
#define LOG_CLI_ENABLE
#include "debug.h"
#if (CONFIG_BT_MESH_GEN_LIGHTNESS_SRV)
#define LVL_TO_LIGHT(_lvl) (from_actual((_lvl) + 32768))
#define LIGHT_TO_LVL(_light) (to_actual((_light)) - 32768)
struct light_state {
u16 lightness_actual;
u16 lightness_linear;
u16 lightness_default;
u16 lightness_last;
u16 lightness_range_min;
u16 lightness_range_max;
u8_t led_gpio_pin;
};
struct light_state dev_light_state = {
.lightness_actual = 0,
.lightness_last = 0,
.led_gpio_pin = IO_PORTB_07,
};
/** Persistent storage handling */
struct bt_mesh_lightness_srv_settings_data {
struct bt_mesh_lightness_range range;
uint16_t default_light;
#if !IS_ENABLED(CONFIG_EMDS)
uint16_t last;
bool is_on;
#endif
} __attribute__((__packed__));
static const char *const repr_str[] = { "Actual", "Linear" };
#if CONFIG_BT_SETTINGS
static void bt_mesh_lightness_srv_pending_store(struct bt_mesh_model *model)
{
struct bt_mesh_lightness_srv *srv = model->rt->user_data;
struct bt_mesh_lightness_srv_settings_data data = {
.default_light = srv->default_light,
.range = srv->range,
#if !IS_ENABLED(CONFIG_EMDS)
.last = srv->transient.last,
.is_on = srv->transient.is_on,
#endif
};
#if !IS_ENABLED(CONFIG_EMDS)
log_debug("Store: Last: %u Default: %u State: %s Range: [%u - %u]",
data.last, data.default_light, data.is_on ? "On" : "Off",
data.range.min, data.range.max);
#else
log_debug("Store: Default: %u Range: [%u - %u]",
data.default_light, data.range.min, data.range.max);
#endif
#if 0 // not adapter.
(void)bt_mesh_model_data_store(srv->lightness_model, false, NULL,
&data, sizeof(data));
#endif
}
#endif
static void store_state(struct bt_mesh_lightness_srv *srv)
{
#if CONFIG_BT_SETTINGS
bt_mesh_model_data_store_schedule(srv->lightness_model);
#endif
}
static void lvl_status_encode(struct net_buf_simple *buf,
const struct bt_mesh_lightness_status *status,
enum light_repr repr)
{
bt_mesh_model_msg_init(buf, op_get(LIGHTNESS_OP_TYPE_STATUS, repr));
net_buf_simple_add_le16(buf, light_to_repr(status->current, repr));
if (status->remaining_time == 0) {
return;
}
net_buf_simple_add_le16(buf, light_to_repr(status->target, repr));
net_buf_simple_add_u8(buf,
model_transition_encode(status->remaining_time));
}
static int pub(struct bt_mesh_lightness_srv *srv, struct bt_mesh_msg_ctx *ctx,
const struct bt_mesh_lightness_status *status,
enum light_repr repr)
{
log_debug("%u -> %u [%u ms]", status->current, status->target,
status->remaining_time);
if (ctx == NULL) {
/* We'll always make an attempt to publish to the underlying
* models as well, since a publish message typically indicates
* a status change, which should always be published.
* Ignoring the status codes, so that we're able to publish
* in the highest model even if the underlying models don't have
* publishing configured.
*/
struct bt_mesh_lvl_status lvl_status;
lvl_status.current = LIGHT_TO_LVL(status->current);
lvl_status.target = LIGHT_TO_LVL(status->target);
lvl_status.remaining_time = status->remaining_time;
(void)bt_mesh_lvl_srv_pub(&srv->lvl, NULL, &lvl_status);
struct bt_mesh_onoff_status onoff_status;
onoff_status.present_on_off = status->current > 0;
onoff_status.target_on_off = status->target > 0;
onoff_status.remaining_time = status->remaining_time;
(void)bt_mesh_onoff_srv_pub(&srv->ponoff.onoff, NULL,
&onoff_status);
}
BT_MESH_MODEL_BUF_DEFINE(msg, BT_MESH_LIGHTNESS_OP_STATUS,
BT_MESH_LIGHTNESS_MSG_MAXLEN_STATUS);
lvl_status_encode(&msg, status, repr);
return bt_mesh_msg_send(srv->lightness_model, ctx, &msg);
}
static void rsp_lightness_status(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct bt_mesh_lightness_status *status,
enum light_repr repr)
{
BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_LIGHTNESS_OP_STATUS,
BT_MESH_LIGHTNESS_MSG_MAXLEN_STATUS);
lvl_status_encode(&rsp, status, repr);
log_debug("Light %s Response: %u -> %u [%u ms]", repr_str[repr],
light_to_repr(status->current, repr),
light_to_repr(status->target, repr),
status->remaining_time);
bt_mesh_model_send(model, ctx, &rsp, NULL, NULL);
}
static int handle_light_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf, enum light_repr repr)
{
log_debug("%s", repr_str[repr]);
struct bt_mesh_lightness_srv *srv = model->rt->user_data;
struct bt_mesh_lightness_status status = { 0 };
srv->handlers->light_get(srv, ctx, &status);
rsp_lightness_status(model, ctx, &status, repr);
return 0;
}
static int handle_actual_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
return handle_light_get(model, ctx, buf, ACTUAL);
}
static int handle_linear_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
return handle_light_get(model, ctx, buf, LINEAR);
}
void lightness_srv_disable_control(struct bt_mesh_lightness_srv *srv)
{
#if (CONFIG_BT_MESH_LIGHT_CTRL_SRV)
if (srv->ctrl) {
bt_mesh_light_ctrl_srv_disable(srv->ctrl);
}
#endif
}
void bt_mesh_lightness_srv_set(struct bt_mesh_lightness_srv *srv,
struct bt_mesh_msg_ctx *ctx,
struct bt_mesh_lightness_set *set,
struct bt_mesh_lightness_status *status)
{
if (bt_mesh_model_transition_time(set->transition)) {
log_debug("%u [%u + %u ms]", set->lvl, set->transition->delay,
set->transition->time);
} else {
log_debug("%u", set->lvl);
}
memset(status, 0, sizeof(*status));
if (set->lvl != 0) {
set->lvl = CLAMP(set->lvl, srv->range.min, srv->range.max);
}
srv->transient.is_on = (set->lvl > 0);
srv->handlers->light_set(srv, ctx, set, status);
}
void lightness_srv_change_lvl(struct bt_mesh_lightness_srv *srv,
struct bt_mesh_msg_ctx *ctx,
struct bt_mesh_lightness_set *set,
struct bt_mesh_lightness_status *status,
bool publish)
{
bool state_change = (srv->transient.is_on == (set->lvl == 0));
bt_mesh_lightness_srv_set(srv, ctx, set, status);
if (set->lvl != 0) {
state_change |= (srv->transient.last != set->lvl);
srv->transient.last = set->lvl;
}
if (!IS_ENABLED(CONFIG_EMDS) && state_change) {
store_state(srv);
}
if (publish) {
log_debug("Publishing Light %s to 0x%04x", repr_str[ACTUAL], srv->pub.addr);
/* Publishing is always done as an Actual, according to test spec. */
pub(srv, NULL, status, ACTUAL);
}
}
static int lightness_set(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf, bool ack, enum light_repr repr)
{
if (buf->len != BT_MESH_LIGHTNESS_MSG_MINLEN_SET &&
buf->len != BT_MESH_LIGHTNESS_MSG_MAXLEN_SET) {
return -EMSGSIZE;
}
struct bt_mesh_lightness_srv *srv = model->rt->user_data;
struct bt_mesh_model_transition transition;
struct bt_mesh_lightness_status status;
struct bt_mesh_lightness_set set;
uint8_t tid;
set.lvl = repr_to_light(net_buf_simple_pull_le16(buf), repr);
tid = net_buf_simple_pull_u8(buf);
set.transition = model_transition_get(model, &transition, buf);
if (bt_mesh_model_transition_time(set.transition)) {
log_debug("Light set %s: %u [%u + %u ms]", repr_str[repr], set.lvl,
set.transition->delay, set.transition->time);
} else {
log_debug("Light set %s: %u", repr_str[repr], set.lvl);
}
if (!tid_check_and_update(&srv->tid, tid, ctx)) {
/* According to the Mesh Model Specification section 6.2.3.1,
* manual changes to the lightness should disable control.
*/
lightness_srv_disable_control(srv);
lightness_srv_change_lvl(srv, ctx, &set, &status, true);
if (IS_ENABLED(CONFIG_BT_MESH_SCENE_SRV)) {
bt_mesh_scene_invalidate(srv->lightness_model);
}
} else if (ack) {
srv->handlers->light_get(srv, NULL, &status);
}
if (ack) {
rsp_lightness_status(model, ctx, &status, repr);
}
return 0;
}
static int handle_actual_set(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
return lightness_set(model, ctx, buf, true, ACTUAL);
}
static int handle_actual_set_unack(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
return lightness_set(model, ctx, buf, false, ACTUAL);
}
static int handle_linear_set(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
return lightness_set(model, ctx, buf, true, LINEAR);
}
static int handle_linear_set_unack(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
return lightness_set(model, ctx, buf, false, LINEAR);
}
static int handle_last_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
struct bt_mesh_lightness_srv *srv = model->rt->user_data;
BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_LIGHTNESS_OP_LAST_STATUS,
BT_MESH_LIGHTNESS_MSG_LEN_LAST_STATUS);
bt_mesh_model_msg_init(&rsp, BT_MESH_LIGHTNESS_OP_LAST_STATUS);
net_buf_simple_add_le16(&rsp, to_actual(srv->transient.last));
bt_mesh_model_send(model, ctx, &rsp, NULL, NULL);
return 0;
}
static int handle_default_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
struct bt_mesh_lightness_srv *srv = model->rt->user_data;
BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_LIGHTNESS_OP_DEFAULT_STATUS,
BT_MESH_LIGHTNESS_MSG_LEN_DEFAULT_STATUS);
bt_mesh_model_msg_init(&rsp, BT_MESH_LIGHTNESS_OP_DEFAULT_STATUS);
net_buf_simple_add_le16(&rsp, to_actual(srv->default_light));
bt_mesh_model_send(model, ctx, &rsp, NULL, NULL);
return 0;
}
void lightness_srv_default_set(struct bt_mesh_lightness_srv *srv,
struct bt_mesh_msg_ctx *ctx, uint16_t set)
{
uint16_t old = srv->default_light;
if (set == old) {
return;
}
log_debug("%u", set);
srv->default_light = set;
if (srv->handlers->default_update) {
srv->handlers->default_update(srv, ctx, old, set);
}
store_state(srv);
}
static int set_default(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf, bool ack)
{
struct bt_mesh_lightness_srv *srv = model->rt->user_data;
uint16_t new = from_actual(net_buf_simple_pull_le16(buf));
lightness_srv_default_set(srv, ctx, new);
if (!ack) {
return 0;
}
BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_LIGHTNESS_OP_DEFAULT_STATUS,
BT_MESH_LIGHTNESS_MSG_LEN_DEFAULT_STATUS);
bt_mesh_model_msg_init(&rsp, BT_MESH_LIGHTNESS_OP_DEFAULT_STATUS);
net_buf_simple_add_le16(&rsp, srv->default_light);
bt_mesh_model_send(model, ctx, &rsp, NULL, NULL);
return 0;
}
static int handle_default_set(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
return set_default(model, ctx, buf, true);
}
static int handle_default_set_unack(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
return set_default(model, ctx, buf, false);
}
static int handle_range_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
struct bt_mesh_lightness_srv *srv = model->rt->user_data;
BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_LIGHTNESS_OP_RANGE_STATUS,
BT_MESH_LIGHTNESS_MSG_LEN_RANGE_STATUS);
bt_mesh_model_msg_init(&rsp, BT_MESH_LIGHTNESS_OP_RANGE_STATUS);
net_buf_simple_add_u8(&rsp, BT_MESH_MODEL_SUCCESS);
net_buf_simple_add_le16(&rsp, to_actual(srv->range.min));
net_buf_simple_add_le16(&rsp, to_actual(srv->range.max));
bt_mesh_model_send(model, ctx, &rsp, NULL, NULL);
return 0;
}
static int set_range(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf, bool ack)
{
struct bt_mesh_lightness_srv *srv = model->rt->user_data;
struct bt_mesh_lightness_range new;
new.min = from_actual(net_buf_simple_pull_le16(buf));
new.max = from_actual(net_buf_simple_pull_le16(buf));
/* The test specification doesn't accept changes to the status, even if
* the parameters are wrong. Simply ignore messages with wrong
* parameters.
*/
if (new.min == 0 || new.max == 0 || new.min > new.max) {
return -EINVAL;
}
if (new.min != srv->range.min || new.max != srv->range.max) {
struct bt_mesh_lightness_range old = srv->range;
srv->range = new;
if (srv->handlers->range_update) {
srv->handlers->range_update(srv, ctx, &old, &new);
}
store_state(srv);
}
if (!ack) {
return 0;
}
BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_LIGHTNESS_OP_RANGE_STATUS,
BT_MESH_LIGHTNESS_MSG_LEN_RANGE_STATUS);
bt_mesh_model_msg_init(&rsp, BT_MESH_LIGHTNESS_OP_RANGE_STATUS);
net_buf_simple_add_u8(&rsp, BT_MESH_MODEL_SUCCESS);
net_buf_simple_add_le16(&rsp, to_actual(srv->range.min));
net_buf_simple_add_le16(&rsp, to_actual(srv->range.max));
bt_mesh_model_send(model, ctx, &rsp, NULL, NULL);
return 0;
}
static int handle_range_set(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
return set_range(model, ctx, buf, true);
}
static int handle_range_set_unack(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
return set_range(model, ctx, buf, false);
}
const struct bt_mesh_model_op _bt_mesh_lightness_srv_op[] = {
{
BT_MESH_LIGHTNESS_OP_GET,
BT_MESH_LEN_EXACT(BT_MESH_LIGHTNESS_MSG_LEN_GET),
handle_actual_get,
},
{
BT_MESH_LIGHTNESS_OP_SET,
BT_MESH_LEN_MIN(BT_MESH_LIGHTNESS_MSG_MINLEN_SET),
handle_actual_set,
},
{
BT_MESH_LIGHTNESS_OP_SET_UNACK,
BT_MESH_LEN_MIN(BT_MESH_LIGHTNESS_MSG_MINLEN_SET),
handle_actual_set_unack,
},
{
BT_MESH_LIGHTNESS_OP_LINEAR_GET,
BT_MESH_LEN_EXACT(BT_MESH_LIGHTNESS_MSG_LEN_GET),
handle_linear_get,
},
{
BT_MESH_LIGHTNESS_OP_LINEAR_SET,
BT_MESH_LEN_MIN(BT_MESH_LIGHTNESS_MSG_MINLEN_SET),
handle_linear_set,
},
{
BT_MESH_LIGHTNESS_OP_LINEAR_SET_UNACK,
BT_MESH_LEN_MIN(BT_MESH_LIGHTNESS_MSG_MINLEN_SET),
handle_linear_set_unack,
},
{
BT_MESH_LIGHTNESS_OP_LAST_GET,
BT_MESH_LEN_EXACT(BT_MESH_LIGHTNESS_MSG_LEN_LAST_GET),
handle_last_get,
},
{
BT_MESH_LIGHTNESS_OP_DEFAULT_GET,
BT_MESH_LEN_EXACT(BT_MESH_LIGHTNESS_MSG_LEN_DEFAULT_GET),
handle_default_get,
},
{
BT_MESH_LIGHTNESS_OP_RANGE_GET,
BT_MESH_LEN_EXACT(BT_MESH_LIGHTNESS_MSG_LEN_RANGE_GET),
handle_range_get,
},
BT_MESH_MODEL_OP_END,
};
const struct bt_mesh_model_op _bt_mesh_lightness_setup_srv_op[] = {
{
BT_MESH_LIGHTNESS_OP_DEFAULT_SET,
BT_MESH_LEN_EXACT(BT_MESH_LIGHTNESS_MSG_LEN_DEFAULT_SET),
handle_default_set,
},
{
BT_MESH_LIGHTNESS_OP_DEFAULT_SET_UNACK,
BT_MESH_LEN_EXACT(BT_MESH_LIGHTNESS_MSG_LEN_DEFAULT_SET),
handle_default_set_unack,
},
{
BT_MESH_LIGHTNESS_OP_RANGE_SET,
BT_MESH_LEN_EXACT(BT_MESH_LIGHTNESS_MSG_LEN_RANGE_SET),
handle_range_set,
},
{
BT_MESH_LIGHTNESS_OP_RANGE_SET_UNACK,
BT_MESH_LEN_EXACT(BT_MESH_LIGHTNESS_MSG_LEN_RANGE_SET),
handle_range_set_unack,
},
BT_MESH_MODEL_OP_END,
};
static void lvl_get(struct bt_mesh_lvl_srv *lvl_srv,
struct bt_mesh_msg_ctx *ctx, struct bt_mesh_lvl_status *rsp)
{
struct bt_mesh_lightness_srv *srv =
CONTAINER_OF(lvl_srv, struct bt_mesh_lightness_srv, lvl);
struct bt_mesh_lightness_status status = { 0 };
srv->handlers->light_get(srv, ctx, &status);
rsp->current = LIGHT_TO_LVL(status.current);
rsp->target = LIGHT_TO_LVL(status.target);
rsp->remaining_time = status.remaining_time;
log_debug("%i -> %i [%u ms]", rsp->current, rsp->target,
rsp->remaining_time);
}
static void lvl_set(struct bt_mesh_lvl_srv *lvl_srv,
struct bt_mesh_msg_ctx *ctx,
const struct bt_mesh_lvl_set *lvl_set,
struct bt_mesh_lvl_status *rsp)
{
log_info_hexdump(lvl_set, sizeof(struct bt_mesh_lvl_set));
struct bt_mesh_lightness_srv *srv =
CONTAINER_OF(lvl_srv, struct bt_mesh_lightness_srv, lvl);
struct bt_mesh_lightness_set set = {
.lvl = LVL_TO_LIGHT(lvl_set->lvl),
.transition = lvl_set->transition,
};
struct bt_mesh_lightness_status status = { 0 };
if (lvl_set->new_transaction) {
/* According to the Mesh Model Specification section 6.2.3.1,
* manual changes to the lightness should disable control.
*/
lightness_srv_disable_control(srv);
lightness_srv_change_lvl(srv, ctx, &set, &status, true);
} else if (rsp) {
srv->handlers->light_get(srv, NULL, &status);
}
if (rsp) {
rsp->current = LIGHT_TO_LVL(status.current);
rsp->target = LIGHT_TO_LVL(status.target);
rsp->remaining_time = status.remaining_time;
log_debug("%i -> %i [%u ms]", rsp->current, rsp->target,
rsp->remaining_time);
}
}
static void lvl_delta_set(struct bt_mesh_lvl_srv *lvl_srv,
struct bt_mesh_msg_ctx *ctx,
const struct bt_mesh_lvl_delta_set *delta_set,
struct bt_mesh_lvl_status *rsp)
{
struct bt_mesh_lightness_srv *srv =
CONTAINER_OF(lvl_srv, struct bt_mesh_lightness_srv, lvl);
struct bt_mesh_lightness_status status = { 0 };
int32_t target_actual;
if (delta_set->new_transaction) {
srv->handlers->light_get(srv, NULL, &status);
/* Delta lvl is bound to the lightness actual state, so the
* calculation must happen in that space:
*/
srv->delta_start = to_actual(status.current);
}
if (srv->delta_start == 0 && delta_set->delta <= 0) {
/* Do not clamp to range when dimming down from zero, to avoid
* the light to turn on when dimmed down.
*/
target_actual = 0;
} else {
/* Clamp the value to the lightness range to prevent that it moves
* back to zero in binding with Generic Level state.
*/
target_actual = CLAMP(srv->delta_start + delta_set->delta,
to_actual(srv->range.min), to_actual(srv->range.max));
}
struct bt_mesh_lightness_set set = {
/* Converting back to configured space: */
.lvl = from_actual(target_actual),
.transition = delta_set->transition,
};
/* According to the Mesh Model Specification section 6.2.3.1,
* manual changes to the lightness should disable control.
*/
lightness_srv_disable_control(srv);
lightness_srv_change_lvl(srv, ctx, &set, &status, true);
if (rsp) {
rsp->current = LIGHT_TO_LVL(status.current);
rsp->target = LIGHT_TO_LVL(status.target);
rsp->remaining_time = status.remaining_time;
log_debug("Delta set rsp: %i (light: %u) -> %i (light: %u) [%u ms]",
rsp->current, status.current, rsp->target, status.target,
status.remaining_time);
}
}
static void lvl_move_set(struct bt_mesh_lvl_srv *lvl_srv,
struct bt_mesh_msg_ctx *ctx,
const struct bt_mesh_lvl_move_set *move_set,
struct bt_mesh_lvl_status *rsp)
{
log_info_hexdump(move_set, sizeof(struct bt_mesh_lvl_move_set));
struct bt_mesh_lightness_srv *srv =
CONTAINER_OF(lvl_srv, struct bt_mesh_lightness_srv, lvl);
struct bt_mesh_lightness_status status = { 0 };
uint16_t target;
srv->handlers->light_get(srv, NULL, &status);
if (move_set->delta > 0) {
target = srv->range.max;
} else if (move_set->delta < 0) {
target = status.current == 0 ? 0 : srv->range.min;
} else {
target = status.current;
}
struct bt_mesh_model_transition transition;
struct bt_mesh_lightness_set set = {
.lvl = target,
.transition = NULL,
};
if (move_set->delta != 0 && move_set->transition && target != status.current) {
uint32_t distance = abs(target - status.current);
if (status.current == 0) {
/* Subtract RANGE_MIN when dimming up from zero to avoid taking
* the "jumped" distance between 0 and RANGE_MIN into account
* when computing the timing.
*/
distance -= srv->range.min;
}
/* Note: We're not actually converting from the lightness actual
* space to the linear space here, even if configured. This
* means that a generic level server communicating with a
* lightness server running in linear space will see the
* server's transition as non-linear. The transition time and
* end points are unaffected by this.
*/
uint32_t time_to_edge = ((uint64_t)distance *
(uint64_t)move_set->transition->time) /
abs(move_set->delta);
log_debug("Move: distance: %u delta: %i step: %u ms time: %u ms",
(uint32_t)distance, move_set->delta,
move_set->transition->time, time_to_edge);
if (time_to_edge > 0) {
transition.delay = move_set->transition->delay;
transition.time = time_to_edge;
set.transition = &transition;
}
}
/* According to the Mesh Model Specification section 6.2.3.1,
* manual changes to the lightness should disable control.
*/
lightness_srv_disable_control(srv);
lightness_srv_change_lvl(srv, ctx, &set, &status, true);
if (rsp) {
rsp->current = LIGHT_TO_LVL(status.current);
rsp->target = LIGHT_TO_LVL(status.target);
rsp->remaining_time = status.remaining_time;
log_debug("Move set rsp: %i (light: %u) -> %i (light: %u) [%u ms]",
rsp->current, status.current, rsp->target, status.target,
status.remaining_time);
}
}
const struct bt_mesh_lvl_srv_handlers _bt_mesh_lightness_srv_lvl_handlers = {
.get = lvl_get,
.set = lvl_set,
.delta_set = lvl_delta_set,
.move_set = lvl_move_set,
};
static void onoff_set(struct bt_mesh_onoff_srv *onoff_srv,
struct bt_mesh_msg_ctx *ctx,
const struct bt_mesh_onoff_set *onoff_set,
struct bt_mesh_onoff_status *rsp)
{
struct bt_mesh_lightness_srv *srv = CONTAINER_OF(
onoff_srv, struct bt_mesh_lightness_srv, ponoff.onoff);
struct bt_mesh_lightness_set set = {
.transition = onoff_set->transition,
};
struct bt_mesh_lightness_status status;
if (onoff_set->on_off) {
set.lvl = (srv->default_light ? srv->default_light : srv->transient.last);
} else {
set.lvl = 0;
}
/* According to the Mesh Model Specification section 6.2.3.1,
* manual changes to the lightness should disable control.
*/
lightness_srv_disable_control(srv);
lightness_srv_change_lvl(srv, ctx, &set, &status, true);
if (rsp) {
rsp->present_on_off = (status.current > 0);
rsp->remaining_time = status.remaining_time;
rsp->target_on_off = (status.target > 0);
}
}
static void onoff_get(struct bt_mesh_onoff_srv *onoff_srv,
struct bt_mesh_msg_ctx *ctx,
struct bt_mesh_onoff_status *rsp)
{
struct bt_mesh_lightness_srv *srv = CONTAINER_OF(
onoff_srv, struct bt_mesh_lightness_srv, ponoff.onoff);
struct bt_mesh_lightness_status status = { 0 };
srv->handlers->light_get(srv, ctx, &status);
rsp->present_on_off = (status.current > 0);
rsp->remaining_time = status.remaining_time;
rsp->target_on_off = (status.target > 0);
}
const struct bt_mesh_onoff_srv_handlers _bt_mesh_lightness_srv_onoff_handlers = {
.set = onoff_set,
.get = onoff_get,
};
static void lightness_srv_reset(struct bt_mesh_lightness_srv *srv)
{
srv->range.min = 1;
srv->range.max = UINT16_MAX;
srv->default_light = 0;
srv->transient.last = UINT16_MAX;
srv->transient.is_on = false;
}
static void bt_mesh_lightness_srv_reset(struct bt_mesh_model *model)
{
struct bt_mesh_lightness_srv *srv = model->rt->user_data;
lightness_srv_reset(srv);
net_buf_simple_reset(srv->pub.msg);
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
(void)bt_mesh_model_data_store(srv->lightness_model, false,
NULL, NULL, 0);
}
}
static ssize_t scene_store(struct bt_mesh_model *model, uint8_t data[])
{
struct bt_mesh_lightness_srv *srv = model->rt->user_data;
struct bt_mesh_lightness_status status = { 0 };
srv->handlers->light_get(srv, NULL, &status);
sys_put_le16(to_actual(status.remaining_time ? status.target : status.current), &data[0]);
return 2;
}
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_lightness_srv *srv = model->rt->user_data;
struct bt_mesh_lightness_status dummy_status;
struct bt_mesh_lightness_set set = {
.lvl = from_actual(sys_get_le16(data)),
.transition = transition,
};
lightness_srv_change_lvl(srv, NULL, &set, &dummy_status, false);
}
static void scene_recall_complete(struct bt_mesh_model *model)
{
struct bt_mesh_lightness_srv *srv = model->rt->user_data;
struct bt_mesh_lightness_status status;
srv->handlers->light_get(srv, NULL, &status);
(void)pub(srv, NULL, &status, ACTUAL);
}
/* 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(lightness) = {
// .id.sig = BT_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV,
// .maxlen = 2,
// .store = scene_store,
// .recall = scene_recall,
// .recall_complete = scene_recall_complete,
// };
const struct bt_mesh_scene_entry bt_mesh_scene_entry_sig_lightness = {
.id.sig = BT_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV,
.maxlen = 2,
.store = scene_store,
.recall = scene_recall,
};
static int update_handler(struct bt_mesh_model *model)
{
struct bt_mesh_lightness_srv *srv = model->rt->user_data;
struct bt_mesh_lightness_status status = { 0 };
srv->handlers->light_get(srv, NULL, &status);
log_debug("Republishing: %u -> %u [%u ms]", status.current, status.target,
status.remaining_time);
lvl_status_encode(model->pub->msg, &status, ACTUAL);
return 0;
}
static int bt_mesh_lightness_srv_init(struct bt_mesh_model *model)
{
struct bt_mesh_lightness_srv *srv = model->rt->user_data;
int err;
srv->lightness_model = model;
lightness_srv_reset(srv);
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 IS_ENABLED(CONFIG_BT_SETTINGS) && IS_ENABLED(CONFIG_EMDS)
srv->emds_entry.entry.id = EMDS_MODEL_ID(model);
srv->emds_entry.entry.data = (uint8_t *)&srv->transient;
srv->emds_entry.entry.len = sizeof(srv->transient);
err = emds_entry_add(&srv->emds_entry);
if (err) {
return err;
}
#endif
#if CONFIG_BT_MESH_MODEL_EXTENSIONS
err = bt_mesh_model_extend(model, srv->ponoff.ponoff_model);
if (err) {
return err;
}
return bt_mesh_model_extend(model, srv->lvl.model);
#else
return 0;
#endif
}
#if CONFIG_BT_SETTINGS
static int bt_mesh_lightness_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_lightness_srv *srv = model->rt->user_data;
struct bt_mesh_lightness_srv_settings_data data;
ssize_t result;
if (name) {
return -ENOENT;
}
if (!read_cb) {
return -EINVAL;
}
result = read_cb(cb_arg, &data, sizeof(data));
if (result <= 0) {
return result;
} else if (result < sizeof(data)) {
return -EINVAL;
}
srv->default_light = data.default_light;
srv->range = data.range;
#if !IS_ENABLED(CONFIG_EMDS)
srv->transient.last = data.last;
srv->transient.is_on = data.is_on;
log_debug("Set: Last: %u Default: %u State: %s Range: [%u - %u]",
srv->transient.last, srv->default_light, data.is_on ? "On" : "Off",
srv->range.min, srv->range.max);
#else
log_debug("Set: Default: %u Range: [%u - %u]",
srv->default_light, srv->range.min, srv->range.max);
#endif
return 0;
}
#endif
int lightness_on_power_up(struct bt_mesh_lightness_srv *srv)
{
struct bt_mesh_lightness_status dummy = { 0 };
struct bt_mesh_model_transition transition = {
.time = srv->ponoff.dtt.transition_time,
};
struct bt_mesh_lightness_set set = { .transition = &transition };
switch (srv->ponoff.on_power_up) {
case BT_MESH_ON_POWER_UP_OFF:
break;
case BT_MESH_ON_POWER_UP_ON:
set.lvl = (srv->default_light ? srv->default_light : srv->transient.last);
break;
case BT_MESH_ON_POWER_UP_RESTORE:
if (!srv->transient.is_on) {
return 0;
}
set.lvl = srv->transient.last;
break;
default:
return -EINVAL;
}
log_debug("Loading POnOff: %u -> %u [%u ms]", srv->ponoff.on_power_up,
set.lvl, transition.time);
lightness_srv_change_lvl(srv, NULL, &set, &dummy, true);
return 0;
}
#if CONFIG_BT_SETTINGS
static int bt_mesh_lightness_srv_start(struct bt_mesh_model *model)
{
struct bt_mesh_lightness_srv *srv = model->rt->user_data;
/* When Light Lightness server is extended by Light LC server, Light LC server will execute
* power-up sequence of Light Lightness server according to section 6.5.1.2. Otherwise,
* Light Lightness will execute power-up sequence behavior.
*/
if (atomic_test_bit(&srv->flags, LIGHTNESS_SRV_FLAG_EXTENDED_BY_LIGHT_CTRL)) {
return 0;
}
return lightness_on_power_up(srv);
}
#endif
const struct bt_mesh_model_cb _bt_mesh_lightness_srv_cb = {
.init = bt_mesh_lightness_srv_init,
.reset = bt_mesh_lightness_srv_reset,
#if CONFIG_BT_SETTINGS
.settings_set = bt_mesh_lightness_srv_settings_set,
.start = bt_mesh_lightness_srv_start,
.pending_store = bt_mesh_lightness_srv_pending_store,
#endif
};
static int bt_mesh_lightness_setup_srv_init(struct bt_mesh_model *model)
{
struct bt_mesh_lightness_srv *srv = model->rt->user_data;
int err;
srv->lightness_setup_model = model;
#if CONFIG_BT_MESH_MODEL_EXTENSIONS
err = bt_mesh_model_extend(model, srv->lightness_model);
if (err) {
return err;
}
#endif
#if (CONFIG_BT_MESH_COMP_PAGE_1)
err = bt_mesh_model_correspond(model, srv->lightness_model);
if (err) {
return err;
}
#endif
#if CONFIG_BT_MESH_MODEL_EXTENSIONS
return bt_mesh_model_extend(model, srv->ponoff.ponoff_setup_model);
#else
return 0;
#endif
}
const struct bt_mesh_model_cb _bt_mesh_lightness_setup_srv_cb = {
.init = bt_mesh_lightness_setup_srv_init,
};
int bt_mesh_lightness_srv_pub(struct bt_mesh_lightness_srv *srv,
struct bt_mesh_msg_ctx *ctx,
const struct bt_mesh_lightness_status *status)
{
return pub(srv, ctx, status, LIGHT_USER_REPR);
}
#endif