tizen 5.0 migration
[apps/native/co2-meter.git] / src / resource / resource_co2_sensor.c
1 /*
2  * Copyright (c) 2018 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <stdio.h>
18 #include <unistd.h>
19 #include <math.h>
20 #include <app_common.h>
21 #include "smartthings_resource.h"
22 #include "resource/resource_co2_sensor.h"
23 #include "log.h"
24
25 #define CO2_DATA                        "co2_data"      // save co2 data
26
27 #define MAX_PATH_LEN            128
28 #define DC_GAIN                         (8.500)         // refer to schematic, (R2 + R3) / R2 = 8.5
29 #define DEFAULT_ZERO_VOLTS      (2.950)
30 #define DEFAULT_RANGE_VOLTS     (0.400)         // refer to datasheet
31 #define DEFAULT_NOTIFY_TIME     (100)           // 1000msec
32
33 #define ADC_PIN                         0                       // adc pin number
34 #define ADC_ERROR                       (-9999)         // adc read error
35 #define ADC_MAX_VOLT            (4096.f)        // 12bit resolution
36 #define ADC_REF_VOLT            (3300.f + 20.f)
37
38 #define SPI_MAX_VOLT            (3200.f)
39 #define SPI_REF_VOLT            (3000.f)
40
41 #define POINT_X_ZERO    (2.60206)       // the start point, log(400)=2.6020606
42 #define POINT_X_MAX             (4.000)         // the start point, log(10000)=4.0
43 #define VOLT_V_ZERO             (0.3245)        // refer to datasheet, 400ppm(V)
44 #define VOLT_V_MAX              (0.2645)        // refer to datasheet, 10000ppm(V)
45 #define VOLT_V_REACT    (VOLT_V_ZERO - VOLT_V_MAX)
46
47 static const char* RES_CAPABILITY_AIRQUALITYSENSOR = "/capability/airQualitySensor/main/0";
48 static const char* PROP_AIRQUALITY = "airQuality";
49
50 static float mg811_co2_slope;   // refer to datasheet, mg811 co2 slope value
51 static sensor_mg811_t mg_sensor;
52
53 co2_sensor_data_t       co2_sensor;
54 int thread_done = 0;
55 extern int32_t g_co2_sensor_value;
56 extern bool g_switch_is_on;
57 extern smartthings_resource_h st_handle;
58
59 extern int resource_read_adc_mcp3008(int ch_num, unsigned int *out_value); /* resource_adc_mcp3008.c */
60 extern int resource_adc_mcp3008_init(void); /* resource_adc_mcp3008.c */
61
62 static int _get_sensor_parameter(int index);
63
64 /*
65  * initial co2 mg811 sensor for ppm caculation
66  */
67 static void _init_co2_mg811_set(float zero_volts, float max_volts)
68 {
69         sensor_mg811_t *sen = &mg_sensor;
70         float reaction_volts;
71
72         if (zero_volts == 0.f)
73                 zero_volts = VOLT_V_ZERO;
74         if (max_volts == 0.f)
75                 max_volts = VOLT_V_MAX;
76
77         sen->zero_point_volts = zero_volts;
78         sen->max_point_volts = max_volts;
79         reaction_volts = VOLT_V_REACT;
80         mg811_co2_slope = reaction_volts / (POINT_X_ZERO - POINT_X_MAX);
81 #if defined(__DEBUG__)
82         _D("CO2Volage zero_volts: %.f mV, max_volts: %.f mV, reaction %.f mV",
83                         sen->zero_point_volts * 1000., sen->max_point_volts * 1000., reaction_volts * 1000.);
84         _D("mg811_ppm: %.3f V, %.3f", POINT_X_ZERO, mg811_co2_slope);
85 #endif
86 }
87
88 /*
89  * get ppm value in co2 mg811 sensor
90  */
91 static int _get_co2_mg811_ppm(float volts)
92 {
93         static int debug = 0;
94         sensor_mg811_t *sen = &mg_sensor;
95         float log_volts = 0;
96
97         volts = volts / DC_GAIN;
98         if (!(volts <= sen->zero_point_volts && volts >= sen->max_point_volts)) {
99                 if ((debug++ % 10) == 0) {
100                         _D("wrong input %.f mV, voltage range %.f ~ %.f (400 ppm ~ 10000 ppm)",
101                                 volts * 1000., sen->zero_point_volts * 1000., sen->max_point_volts * 1000.);
102                 }
103                 if (volts < sen->max_point_volts) {
104                         return 10000;
105                 }
106                 return -1;
107         }
108         log_volts = (volts - sen->zero_point_volts) / mg811_co2_slope;
109
110         return pow(10, log_volts + POINT_X_ZERO);
111 }
112
113 /*
114  * get adc to co2 sensor analog value
115  */
116 static short resource_get_co2_sensor_analog(int ch_num)
117 {
118         int ret = 0;
119         unsigned int out_value = 0;
120         float sensor_value = 0;
121
122         ret = resource_read_adc_mcp3008(ch_num, &out_value);
123         if (ret < 0)
124                 return ret;
125         sensor_value = ((float)out_value * 4.) * (SPI_REF_VOLT / SPI_MAX_VOLT); // 10bit -> 12bit, calibration adc volt
126
127         return (short)sensor_value;
128 }
129
130 /*
131  * update to average co2 ppm value
132  */
133 int resource_update_co2_sensor_value(void)
134 {
135         co2_sensor_data_t *sensorp = &co2_sensor;
136         float sensor_value = 0;
137         float sensor_fvalue = 0;
138         int n, percentage;
139         int size = 0;
140         static int debug = 0;
141
142         MUTEX_LOCK;
143         if (sensorp->bsize < ADC_MAX_SIZE)
144                 size = sensorp->bsize;
145         else
146                 size = ADC_MAX_SIZE;
147         MUTEX_UNLOCK;
148
149         for (n = 0; n < size; n++) {
150                 MUTEX_LOCK;
151                 sensor_value += (float)sensorp->sensor_value[n];
152                 MUTEX_UNLOCK;
153         }
154         sensor_value = sensor_value / (float)size;
155
156         sensor_fvalue = (sensor_value * ADC_REF_VOLT) / ADC_MAX_VOLT;
157         percentage = _get_co2_mg811_ppm(sensor_fvalue / 1000.f);
158
159         if (percentage < 0 || percentage >= 10000) {
160                 if ((debug++ % 5) == 0)
161                         _D("sensor: %.f, volt: %.2f mV, CO2: %d ppm", sensor_value, sensor_fvalue, percentage);
162         }
163
164         return percentage;
165 }
166
167 /*
168  * initial adc funtion
169  */
170 void resource_init_co2_sensor(void)
171 {
172         float zero_volts;       // measurement mutimeter voltage (mV)
173
174         zero_volts = (float)_get_sensor_parameter(0) / 1000.0;
175         if (zero_volts == 0.0)
176                 zero_volts = DEFAULT_ZERO_VOLTS;
177
178         _init_co2_mg811_set(zero_volts/DC_GAIN, (zero_volts - DEFAULT_RANGE_VOLTS)/DC_GAIN);
179 }
180
181 /*
182  * get adc parameters
183  * 0: calibration min voltage
184  * 1: calibration max voltage (no used)
185  * 2: notification loop count (default 100 is 1000msec delay)
186  */
187 int _get_sensor_parameter(int index)
188 {
189         FILE *fp;
190         char buffer[16];
191         int volts[3];
192
193         char path[MAX_PATH_LEN];
194         char *app_data_path = NULL;
195
196         app_data_path = app_get_data_path();
197         sprintf(path, "%s%s", app_data_path, CO2_DATA);
198
199         if((fp = fopen(path, "r")) == NULL) {
200                 _E("Error: [%s] can't open adc data file", path);
201                 if (index == 2)
202                         return 0;
203                 return (int)(DEFAULT_ZERO_VOLTS * 1000);
204         }
205         fgets(buffer, 16, fp);
206         fclose(fp);
207         sscanf(buffer, "%d %d %d", &volts[0], &volts[1], &volts[2]);
208         _D("get parameter: %s, zero: %d, max: %d, count: %d", buffer, volts[0], volts[1], volts[2]);
209
210         free(app_data_path);
211
212         return volts[index];
213 }
214
215 /*
216  * set adc parameters
217  */
218 void resource_set_sensor_parameter(int zero_volts)
219 {
220         FILE *fp;
221         char buffer[16];
222         char path[MAX_PATH_LEN];
223         char *app_data_path = NULL;
224
225         app_data_path = app_get_data_path();
226         sprintf(path, "%s%s", app_data_path, CO2_DATA);
227
228         if((fp = fopen(path, "w+")) == NULL) {
229                 _E("ERROR: can't fopen file: %s", CO2_DATA);
230                 return;
231         }
232         memset(buffer, 0, sizeof(buffer));
233         sprintf(buffer, "%d %d %d", zero_volts, zero_volts - (int)(DEFAULT_RANGE_VOLTS * 1000), DEFAULT_NOTIFY_TIME);
234         fputs(buffer, fp);
235         fclose(fp);
236         free(app_data_path);
237 }
238
239 /*
240  * get co2 sensor percentage (ppm)
241  */
242 int resource_get_co2_sensor_parameter(void)
243 {
244         int param = _get_sensor_parameter(0);
245
246         return param;
247 }
248
249 /*
250  * notify sensor value to cloud
251  */
252 static int notify_sensor_value(void)
253 {
254         int error = SMARTTHINGS_RESOURCE_ERROR_NONE;
255
256         smartthings_payload_h resp_payload = NULL;
257
258         // send notification to cloud server
259         error = smartthings_payload_create(&resp_payload);
260         if (error != SMARTTHINGS_RESOURCE_ERROR_NONE || !resp_payload) {
261                 _E("smartthings_payload_create() failed, [%d]", error);
262                 return error;
263         }
264
265         error = smartthings_payload_set_int(resp_payload, PROP_AIRQUALITY, g_co2_sensor_value);
266         if (error != SMARTTHINGS_RESOURCE_ERROR_NONE) {
267                 _E("smartthings_payload_set_int() failed, [%d]", error);
268                 return error;
269         }
270
271         error = smartthings_resource_notify(st_handle, RES_CAPABILITY_AIRQUALITYSENSOR, resp_payload);
272         if (error != SMARTTHINGS_RESOURCE_ERROR_NONE) {
273                 _E("smartthings_resource_notify() failed, [%d]", error);
274                 return error;
275         }
276
277         if (smartthings_payload_destroy(resp_payload)) {
278                 _E("smartthings_payload_destroy() failed");
279                 return error;
280         }
281
282         return error;
283 }
284
285 /*
286  * main thread function to get sensor data
287  */
288 void *thread_sensor_main(void *arg)
289 {
290         int ret = 0;
291         int pin = ADC_PIN;
292         int err_count = 0;
293         short sensor_value;
294         co2_sensor_data_t *sensorp = &co2_sensor;
295
296         _D("%s starting...\n", __func__);
297
298         memset((void *)sensorp, 0, sizeof(co2_sensor_data_t));
299         resource_init_co2_sensor();
300
301         ret = resource_adc_mcp3008_init();
302         _D("resource_adc_mcp3008_init ret: %d", ret);
303
304         while (true)
305         {
306                 if (thread_done) break;
307
308                 sensor_value = resource_get_co2_sensor_analog(pin);
309                 if (sensor_value >= 0)
310                 {
311                         MUTEX_LOCK;
312                         sensorp->sensor_value[sensorp->index++] = sensor_value;
313                         if (sensorp->index >= ADC_MAX_SIZE)
314                                 sensorp->index = 0;
315                         if (sensorp->bsize < ADC_MAX_SIZE)
316                                 sensorp->bsize++;
317                         sensorp->count++;
318                         MUTEX_UNLOCK;
319                         err_count = 0;
320                         usleep(10);                             // 10usec
321                 }
322                 else
323                 {
324                         if (++err_count >= 100)
325                         {
326                                 memset((void *)sensorp, 0, sizeof(co2_sensor_data_t));
327                         }
328                         usleep(10 * 1000);              // 10msec
329                 }
330         }
331         _D("%s exiting...\n", __func__);
332         pthread_exit((void *) 0);
333 }
334
335 void *thread_sensor_notify(void *arg)
336                         {
337         int count = 0;
338         int nloop = 0;
339
340         count = _get_sensor_parameter(2);
341         if (count < 10)
342                 count = DEFAULT_NOTIFY_TIME;
343         while (true) {
344                 if (thread_done) break;
345
346                 if (nloop++ >= count) {
347                         nloop = 0;
348                         // notify sensor value to server
349                         co2_sensor_data_t *sensorp = &co2_sensor;
350
351                         if (g_co2_sensor_value > 0 && g_co2_sensor_value < 10000)
352                                 _D("CO2 value: %d, count: %d", g_co2_sensor_value, sensorp->count);
353
354                         MUTEX_LOCK;
355                         sensorp->count = 0;
356                         MUTEX_UNLOCK;
357
358                         if (g_switch_is_on) {
359                                 notify_sensor_value();
360                         }
361                 }
362                 usleep(10 * 1000);              // 10msec
363         }
364         _D("%s exiting...\n", __func__);
365         pthread_exit((void *) 0);
366 }
367