567df0eb2d381a490f7aaf7747a22da8d954d25e
[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 "st_things.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 float mg811_co2_slope;   // refer to datasheet, mg811 co2 slope value
49 static sensor_mg811_t mg_sensor;
50
51 co2_sensor_data_t       co2_sensor;
52 int thread_done = 0;
53 extern int32_t g_co2_sensor_value;
54 extern bool g_switch_is_on;
55
56 extern int resource_read_adc_mcp3008(int ch_num, unsigned int *out_value); /* resource_adc_mcp3008.c */
57 extern int resource_adc_mcp3008_init(void); /* resource_adc_mcp3008.c */
58
59 static int _get_sensor_parameter(int index);
60
61 /*
62  * initial co2 mg811 sensor for ppm caculation
63  */
64 static void _init_co2_mg811_set(float zero_volts, float max_volts)
65 {
66         sensor_mg811_t *sen = &mg_sensor;
67         float reaction_volts;
68
69         if (zero_volts == 0.f)
70                 zero_volts = VOLT_V_ZERO;
71         if (max_volts == 0.f)
72                 max_volts = VOLT_V_MAX;
73
74         sen->zero_point_volts = zero_volts;
75         sen->max_point_volts = max_volts;
76         reaction_volts = VOLT_V_REACT;
77         mg811_co2_slope = reaction_volts / (POINT_X_ZERO - POINT_X_MAX);
78 #if defined(__DEBUG__)
79         DBG("CO2Volage zero_volts: %.f mV, max_volts: %.f mV, reaction %.f mV",
80                         sen->zero_point_volts * 1000., sen->max_point_volts * 1000., reaction_volts * 1000.);
81         DBG("mg811_ppm: %.3f V, %.3f", POINT_X_ZERO, mg811_co2_slope);
82 #endif
83 }
84
85 /*
86  * get ppm value in co2 mg811 sensor
87  */
88 static int _get_co2_mg811_ppm(float volts)
89 {
90         static int debug = 0;
91         sensor_mg811_t *sen = &mg_sensor;
92         float log_volts = 0;
93
94         volts = volts / DC_GAIN;
95         if (!(volts <= sen->zero_point_volts && volts >= sen->max_point_volts)) {
96                 if ((debug++ % 10) == 0) {
97                         DBG("wrong input %.f mV, voltage range %.f ~ %.f (400 ppm ~ 10000 ppm)",
98                                 volts * 1000., sen->zero_point_volts * 1000., sen->max_point_volts * 1000.);
99                 }
100                 if (volts < sen->max_point_volts) {
101                         return 10000;
102                 }
103                 return -1;
104         }
105         log_volts = (volts - sen->zero_point_volts) / mg811_co2_slope;
106
107         return pow(10, log_volts + POINT_X_ZERO);
108 }
109
110 /*
111  * get adc to co2 sensor analog value
112  */
113 static short resource_get_co2_sensor_analog(int ch_num)
114 {
115         int ret = 0;
116         unsigned int out_value = 0;
117         float sensor_value = 0;
118
119         ret = resource_read_adc_mcp3008(ch_num, &out_value);
120         if (ret < 0)
121                 return ret;
122         sensor_value = ((float)out_value * 4.) * (SPI_REF_VOLT / SPI_MAX_VOLT); // 10bit -> 12bit, calibration adc volt
123
124         return (short)sensor_value;
125 }
126
127 /*
128  * update to average co2 ppm value
129  */
130 int resource_update_co2_sensor_value(void)
131 {
132         co2_sensor_data_t *sensorp = &co2_sensor;
133         float sensor_value = 0;
134         float sensor_fvalue = 0;
135         int n, percentage;
136         int size = 0;
137         static int debug = 0;
138
139         MUTEX_LOCK;
140         if (sensorp->bsize < ADC_MAX_SIZE)
141                 size = sensorp->bsize;
142         else
143                 size = ADC_MAX_SIZE;
144         MUTEX_UNLOCK;
145
146         for (n = 0; n < size; n++) {
147                 MUTEX_LOCK;
148                 sensor_value += (float)sensorp->sensor_value[n];
149                 MUTEX_UNLOCK;
150         }
151         sensor_value = sensor_value / (float)size;
152
153         sensor_fvalue = (sensor_value * ADC_REF_VOLT) / ADC_MAX_VOLT;
154         percentage = _get_co2_mg811_ppm(sensor_fvalue / 1000.f);
155
156         if (percentage < 0 || percentage >= 10000) {
157                 if ((debug++ % 5) == 0)
158                         DBG("sensor: %.f, volt: %.2f mV, CO2: %d ppm", sensor_value, sensor_fvalue, percentage);
159         }
160
161         return percentage;
162 }
163
164 /*
165  * initial adc funtion
166  */
167 void resource_init_co2_sensor(void)
168 {
169         float zero_volts;       // measurement mutimeter voltage (mV)
170
171         zero_volts = (float)_get_sensor_parameter(0) / 1000.0;
172         if (zero_volts == 0.0)
173                 zero_volts = DEFAULT_ZERO_VOLTS;
174
175         _init_co2_mg811_set(zero_volts/DC_GAIN, (zero_volts - DEFAULT_RANGE_VOLTS)/DC_GAIN);
176 }
177
178 /*
179  * get adc parameters
180  * 0: calibration min voltage
181  * 1: calibration max voltage (no used)
182  * 2: notification loop count (default 100 is 1000msec delay)
183  */
184 int _get_sensor_parameter(int index)
185 {
186         FILE *fp;
187         char buffer[16];
188         int volts[3];
189
190         char path[MAX_PATH_LEN];
191         char *app_data_path = NULL;
192
193         app_data_path = app_get_data_path();
194         sprintf(path, "%s%s", app_data_path, CO2_DATA);
195
196         if((fp = fopen(path, "r")) == NULL) {
197                 ERR("Error: [%s] can't open adc data file", path);
198                 if (index == 2)
199                         return 0;
200                 return (int)(DEFAULT_ZERO_VOLTS * 1000);
201         }
202         fgets(buffer, 16, fp);
203         fclose(fp);
204         sscanf(buffer, "%d %d %d", &volts[0], &volts[1], &volts[2]);
205         DBG("get parameter: %s, zero: %d, max: %d, count: %d", buffer, volts[0], volts[1], volts[2]);
206
207         free(app_data_path);
208
209         return volts[index];
210 }
211
212 /*
213  * set adc parameters
214  */
215 void resource_set_sensor_parameter(int zero_volts)
216 {
217         FILE *fp;
218         char buffer[16];
219         char path[MAX_PATH_LEN];
220         char *app_data_path = NULL;
221
222         app_data_path = app_get_data_path();
223         sprintf(path, "%s%s", app_data_path, CO2_DATA);
224
225         if((fp = fopen(path, "w+")) == NULL) {
226                 ERR("ERROR: can't fopen file: %s", CO2_DATA);
227                 return;
228         }
229         memset(buffer, 0, sizeof(buffer));
230         sprintf(buffer, "%d %d %d", zero_volts, zero_volts - (int)(DEFAULT_RANGE_VOLTS * 1000), DEFAULT_NOTIFY_TIME);
231         fputs(buffer, fp);
232         fclose(fp);
233         free(app_data_path);
234 }
235
236 /*
237  * get co2 sensor percentage (ppm)
238  */
239 int resource_get_co2_sensor_parameter(void)
240 {
241         int param = _get_sensor_parameter(0);
242
243         return param;
244 }
245
246 /*
247  * main thread funtion to get sensor data
248  */
249 void *thread_sensor_main(void *arg)
250 {
251         int ret = 0;
252         int pin = ADC_PIN;
253         int err_count = 0;
254         short sensor_value;
255         co2_sensor_data_t *sensorp = &co2_sensor;
256
257         DBG("%s starting...\n", __func__);
258
259         memset((void *)sensorp, 0, sizeof(co2_sensor_data_t));
260         resource_init_co2_sensor();
261
262         ret = resource_adc_mcp3008_init();
263         DBG("resource_adc_mcp3008_init ret: %d", ret);
264
265         while (true)
266         {
267                 if (thread_done) break;
268
269                 sensor_value = resource_get_co2_sensor_analog(pin);
270                 if (sensor_value >= 0)
271                 {
272                         MUTEX_LOCK;
273                         sensorp->sensor_value[sensorp->index++] = sensor_value;
274                         if (sensorp->index >= ADC_MAX_SIZE)
275                                 sensorp->index = 0;
276                         if (sensorp->bsize < ADC_MAX_SIZE)
277                                 sensorp->bsize++;
278                         sensorp->count++;
279                         MUTEX_UNLOCK;
280                         err_count = 0;
281                         usleep(10);                             // 10usec
282                 }
283                 else
284                 {
285                         if (++err_count >= 100)
286                         {
287                                 memset((void *)sensorp, 0, sizeof(co2_sensor_data_t));
288                         }
289                         usleep(10 * 1000);              // 10msec
290                 }
291         }
292         DBG("%s exiting...\n", __func__);
293         pthread_exit((void *) 0);
294 }
295
296 void *thread_sensor_notify(void *arg)
297                         {
298         int count = 0;
299         int nloop = 0;
300
301         count = _get_sensor_parameter(2);
302         if (count < 10)
303                 count = DEFAULT_NOTIFY_TIME;
304         while (true) {
305                 if (thread_done) break;
306
307                 if (nloop++ >= count) {
308                         nloop = 0;
309                         // notify sensor value to server
310                         co2_sensor_data_t *sensorp = &co2_sensor;
311
312                         if (g_co2_sensor_value > 0 && g_co2_sensor_value < 10000)
313                                 DBG("CO2 value: %d, count: %d", g_co2_sensor_value, sensorp->count);
314
315                         MUTEX_LOCK;
316                         sensorp->count = 0;
317                         MUTEX_UNLOCK;
318
319                         if (g_switch_is_on)
320                                 st_things_notify_observers(RES_CAPABILITY_AIRQUALITYSENSOR);
321                         }
322                 usleep(10 * 1000);              // 10msec
323         }
324         DBG("%s exiting...\n", __func__);
325         pthread_exit((void *) 0);
326 }
327