1 /* Bluetooth: Mesh Generic OnOff, Generic Level, Lighting & Vendor Models
2  *
3  * Copyright (c) 2018 Vikrant More
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <math.h>
9 
10 #include "ble_mesh.h"
11 #include "device_composition.h"
12 #include "state_binding.h"
13 #include "storage.h"
14 #include "transition.h"
15 
ceiling(float num)16 static int32_t ceiling(float num)
17 {
18 	int32_t inum;
19 
20 	inum = (int32_t) num;
21 	if (num == (float) inum) {
22 		return inum;
23 	}
24 
25 	return inum + 1;
26 }
27 
actual_to_linear(uint16_t val)28 static uint16_t actual_to_linear(uint16_t val)
29 {
30 	float tmp;
31 
32 	tmp = ((float) val / UINT16_MAX);
33 
34 	return (uint16_t) ceiling(UINT16_MAX * tmp * tmp);
35 }
36 
linear_to_actual(uint16_t val)37 static uint16_t linear_to_actual(uint16_t val)
38 {
39 	return (uint16_t) (UINT16_MAX * sqrtf(((float) val / UINT16_MAX)));
40 }
41 
constrain_lightness(uint16_t light)42 uint16_t constrain_lightness(uint16_t light)
43 {
44 	if (light > 0 && light < ctl->light->range_min) {
45 		light = ctl->light->range_min;
46 	} else if (light > ctl->light->range_max) {
47 		light = ctl->light->range_max;
48 	}
49 
50 	return light;
51 }
52 
constrain_target_lightness2(void)53 static void constrain_target_lightness2(void)
54 {
55 	/* This is as per Mesh Model Specification 3.3.2.2.3 */
56 	if (ctl->light->target > 0 &&
57 	    ctl->light->target < ctl->light->range_min) {
58 		if (ctl->light->delta < 0) {
59 			ctl->light->target = 0U;
60 		} else {
61 			ctl->light->target = ctl->light->range_min;
62 		}
63 	} else if (ctl->light->target > ctl->light->range_max) {
64 		ctl->light->target = ctl->light->range_max;
65 	}
66 }
67 
constrain_temperature(uint16_t temp)68 uint16_t constrain_temperature(uint16_t temp)
69 {
70 	if (temp < ctl->temp->range_min) {
71 		temp = ctl->temp->range_min;
72 	} else if (temp > ctl->temp->range_max) {
73 		temp = ctl->temp->range_max;
74 	}
75 
76 	return temp;
77 }
78 
light_ctl_temp_to_level(uint16_t temp)79 static int16_t light_ctl_temp_to_level(uint16_t temp)
80 {
81 	float tmp;
82 
83 	/* Mesh Model Specification 6.1.3.1.1 2nd formula start */
84 
85 	tmp = (temp - ctl->temp->range_min) * UINT16_MAX;
86 
87 	tmp = tmp / (ctl->temp->range_max - ctl->temp->range_min);
88 
89 	return (int16_t) (tmp + INT16_MIN);
90 
91 	/* 6.1.3.1.1 2nd formula end */
92 }
93 
level_to_light_ctl_temp(int16_t level)94 uint16_t level_to_light_ctl_temp(int16_t level)
95 {
96 	uint16_t tmp;
97 	float diff;
98 
99 	/* Mesh Model Specification 6.1.3.1.1 1st formula start */
100 	diff = (float) (ctl->temp->range_max - ctl->temp->range_min) /
101 		       UINT16_MAX;
102 
103 	tmp = (uint16_t) ((level - INT16_MIN) * diff);
104 
105 	return (ctl->temp->range_min + tmp);
106 
107 	/* 6.1.3.1.1 1st formula end */
108 }
109 
set_target(uint8_t type,void * dptr)110 void set_target(uint8_t type, void *dptr)
111 {
112 	switch (type) {
113 	case ONOFF: {
114 		uint8_t onoff;
115 
116 		onoff = *((uint8_t *) dptr);
117 		if (onoff == STATE_OFF) {
118 			ctl->light->target = 0U;
119 		} else if (onoff == STATE_ON) {
120 			if (ctl->light->def == 0U) {
121 				ctl->light->target = ctl->light->last;
122 			} else {
123 				ctl->light->target = ctl->light->def;
124 			}
125 		}
126 	}
127 	break;
128 	case LEVEL_LIGHT:
129 		ctl->light->target = *((int16_t *) dptr) - INT16_MIN;
130 		ctl->light->target = constrain_lightness(ctl->light->target);
131 		break;
132 	case DELTA_LEVEL_LIGHT:
133 		ctl->light->target =  *((int16_t *) dptr) - INT16_MIN;
134 		constrain_target_lightness2();
135 		break;
136 	case MOVE_LIGHT:
137 		ctl->light->target = *((uint16_t *) dptr);
138 		break;
139 	case ACTUAL:
140 		ctl->light->target = *((uint16_t *) dptr);
141 		ctl->light->target = constrain_lightness(ctl->light->target);
142 		break;
143 	case LINEAR:
144 		ctl->light->target = linear_to_actual(*((uint16_t *) dptr));
145 		ctl->light->target = constrain_lightness(ctl->light->target);
146 		break;
147 	case CTL_LIGHT:
148 		ctl->light->target = *((uint16_t *) dptr);
149 		ctl->light->target = constrain_lightness(ctl->light->target);
150 		break;
151 	case LEVEL_TEMP:
152 		ctl->temp->target = level_to_light_ctl_temp(*((int16_t *) dptr));
153 		break;
154 	case MOVE_TEMP:
155 		ctl->temp->target = *((uint16_t *) dptr);
156 		break;
157 	case CTL_TEMP:
158 		ctl->temp->target = *((uint16_t *) dptr);
159 		ctl->temp->target = constrain_temperature(ctl->temp->target);
160 		break;
161 	case CTL_DELTA_UV:
162 		ctl->duv->target = *((int16_t *) dptr);
163 		break;
164 	default:
165 		return;
166 	}
167 
168 	if (ctl->onpowerup == STATE_RESTORE) {
169 		save_on_flash(LAST_TARGET_STATES);
170 	}
171 }
172 
get_current(uint8_t type)173 int get_current(uint8_t type)
174 {
175 	switch (type) {
176 	case ONOFF:
177 		if (ctl->light->current != 0U) {
178 			return STATE_ON;
179 		} else {
180 			if (ctl->light->target) {
181 				return STATE_ON;
182 			} else {
183 				return STATE_OFF;
184 			}
185 		}
186 	case LEVEL_LIGHT:
187 		return (int16_t) (ctl->light->current + INT16_MIN);
188 	case ACTUAL:
189 		return ctl->light->current;
190 	case LINEAR:
191 		return actual_to_linear(ctl->light->current);
192 	case CTL_LIGHT:
193 		return ctl->light->current;
194 	case LEVEL_TEMP:
195 		return light_ctl_temp_to_level(ctl->temp->current);
196 	case CTL_TEMP:
197 		return ctl->temp->current;
198 	case CTL_DELTA_UV:
199 		return ctl->duv->current;
200 	default:
201 		return 0U;
202 	}
203 }
204 
get_target(uint8_t type)205 int get_target(uint8_t type)
206 {
207 	switch (type) {
208 	case ONOFF:
209 		if (ctl->light->target != 0U) {
210 			return STATE_ON;
211 		} else {
212 			return STATE_OFF;
213 		}
214 	case LEVEL_LIGHT:
215 		return (int16_t) (ctl->light->target + INT16_MIN);
216 	case ACTUAL:
217 		return ctl->light->target;
218 	case LINEAR:
219 		return actual_to_linear(ctl->light->target);
220 	case CTL_LIGHT:
221 		return ctl->light->target;
222 	case LEVEL_TEMP:
223 		return light_ctl_temp_to_level(ctl->temp->target);
224 	case CTL_TEMP:
225 		return ctl->temp->target;
226 	case CTL_DELTA_UV:
227 		return ctl->duv->target;
228 	default:
229 		return 0U;
230 	}
231 }
232