1
/*
2
 * hald-addon-bme.c: bme addon for Mer http://wiki.maemo.org/Mer
3
 *
4
 * Copyright (C) 2009 Tom Watson <toggles@gmail.com>
5
 *
6
 *  This program is free software: you can redistribute it and/or modify
7
 *  it under the terms of the GNU General Public License as published by
8
 *  the Free Software Foundation, either version 3 of the License, or
9
 *  (at your option) any later version.
10
 *  
11
 *  This program is distributed in the hope that it will be useful,
12
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 *  GNU General Public License for more details.
15
 *  
16
 *  You should have received a copy of the GNU General Public License
17
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 *
19
 */
20
#include "mer-bme.h"
21
#include <errno.h>
22
#include <fcntl.h>
23
#include <stdio.h>
24
#include <unistd.h>
25
#include <stdarg.h>
26
#include <stdlib.h>
27
#include <sys/socket.h>
28
#include <sys/un.h>
29
#include <glib/gmain.h>
30
#include <dbus/dbus-glib-lowlevel.h>
31
#include <hal/libhal.h>
32
#include <ctype.h>
33
34
#define DEBUGx
35
#define DEBUG_FILE      "/tmp/hald-addon-bme.log"
36
37
/*
38
 * Basic operation of this program is to connect to hal and dbus in the
39
 * main function and setup a poll function that is called ~1000ms (default)
40
 * this will attempt to read the new battery status and update hal / dbus
41
 * if the socket hasn't connected to the server it will attempt to reconnect.
42
 */
43
44
const char DefaultSocketPath[] = "/tmp/.bmesrv";
45
const char Handshake[] = "BMentity";
46
const char BMEPath[] = "/com/nokia/bme/signal";
47
const char BMEDot[] = "com.nokia.bme.signal";
48
49
// battery info
50
typedef struct {
51
    uint8 _charger_connected;
52
    uint8 _battery_full;
53
    int _battery_percentage;
54
    uint32 _current;
55
    uint32 _design;
56
    uint32 _last_full;
57
    uint32 _temp_k;
58
    uint32 _standby_time;
59
    uint32 _bars_time;
60
} Battery;
61
62
// global variables
63
typedef struct {
64
    int _socket;
65
    const char *_socket_path;
66
    int _poll_interval;
67
    int _reconnect_count;
68
    int _poll_count;
69
    DBusConnection *_dbus;
70
    LibHalContext *_hal;
71
    const char *_udi;
72
    Battery _battery;
73
} BMEGlobals;
74
75
BMEGlobals Globals;
76
77
// function declarations
78
void close_socket(void);
79
void print(const char *fmt,...);
80
81
/*
82
 * init routine
83
 * Set your defaults in here...
84
 */
85
void init(void)
86
{
87
    memset(&Globals,0,sizeof(Globals));
88
    // set the defaults..
89
    Globals._socket=-1;// invalidate socket
90
    Globals._socket_path=g_strdup(DefaultSocketPath);
91
    Globals._poll_interval=1000;    // 1000 ms
92
    Globals._reconnect_count=10;    // attempt reconnect every 10s
93
    if(getenv("UDI"))
94
    {
95
        Globals._udi=g_strdup(getenv("UDI"));  // device id
96
        print("UDI: %s",Globals._udi);
97
    }
98
    else
99
    {
100
        Globals._udi=g_strdup("/org/freedesktop/Hal/devices/bme");;
101
        print("Defaulting to %s - this is not correct",Globals._udi);
102
    }
103
}
104
105
/*
106
 * print function for logging
107
 */
108
void print(const char *fmt,...)
109
{
110
#ifdef DEBUG
111
    static FILE *fp=0;
112
    va_list va;
113
114
    if(!fp)
115
#ifdef DEBUG_FILE
116
        fp=fopen(DEBUG_FILE,"wt");
117
#else
118
        fp=stdout;
119
#endif
120
121
    fprintf(fp,"hald-addon-bme: ");
122
    va_start(va,fmt);
123
    vfprintf(fp,fmt,va);
124
    va_end(va);
125
    fprintf(fp,"\n");
126
    fflush(0);
127
#else
128
    (void)fmt;
129
#endif
130
}
131
132
/*
133
 * dbus error handler
134
 */
135
void print_dbus_error(const char *text,DBusError *error)
136
{
137
    if(text&&error)
138
    {
139
        dbus_error_is_set(error);
140
        print("%s (%s: %s)\n",text,error->name,error->message);
141
        dbus_error_free(error);
142
    }
143
}
144
145
/*
146
 * bme read
147
 */
148
int bme_read(void *data,uint32 n)
149
{
150
    int i,r,done=0;
151
152
    for(i=0;!done&&i<5;++i)
153
    {
154
        r=recv(Globals._socket,data,n,0);
155
        switch(r)
156
        {
157
            case 0:
158
            {
159
                print("zero length read");
160
                usleep(1000);
161
                //read again
162
                break;
163
            }
164
            case -1:
165
            {
166
                close_socket();
167
                // don't break;
168
            }
169
            default:
170
            {
171
                done=1;
172
                break;
173
            }
174
        }
175
    }
176
    return(r);
177
}
178
179
/*
180
 * connected
181
 */
182
int connected(void)
183
{
184
    return(Globals._socket>=0);
185
}
186
187
/*
188
 * close the socket
189
 */
190
void close_socket(void)
191
{
192
    if(Globals._socket>=0)
193
    {
194
        close(Globals._socket);
195
        Globals._socket=-1;
196
    }
197
}
198
199
/*
200
 * connect the BME socket to the server
201
 */
202
int connect_socket(const char *path)
203
{
204
    int ok=0;
205
    char ch;
206
    struct sockaddr_un sock_address;
207
208
    if(path)
209
    {
210
        memset(&sock_address,0,sizeof(sock_address));
211
        sock_address.sun_family=PF_UNIX;
212
        strcpy(sock_address.sun_path,path);
213
        // create socket
214
        if((Globals._socket=socket(PF_UNIX,SOCK_STREAM,0))>=0)
215
        {
216
            // connect to daemon
217
            if((connect(Globals._socket,
218
                        (struct sockaddr *)&sock_address,
219
                        sizeof(sock_address.sun_family)+strlen(path)))>=0)
220
            {
221
                // hand shake
222
                if(send(Globals._socket,Handshake,strlen(Handshake),0)
223
                                                        ==strlen(Handshake))
224
                {
225
                    if(bme_read(&ch,1)==1)
226
                    {
227
                        if(ch=='\n')
228
                        {
229
                            // whazzzzup??
230
                            ok=1;
231
                        }
232
                        else
233
                        {
234
                            print("Invalid handshake");
235
                        }
236
                    }
237
                    else
238
                    {
239
                        print("Error reading handshake");
240
                    }
241
                }
242
                else
243
                {
244
                    print("Error sending handshake");
245
                }
246
            }
247
            else
248
            {
249
                print("Cannot connect to BME server (%s)",path);
250
            }
251
            // if we failed we need to close the socket..
252
            if(!ok)
253
            {
254
                close_socket();
255
            }
256
        }
257
        else
258
        {
259
            print("Cannot create socket");
260
        }
261
    }
262
    else
263
    {
264
        print("Invalid path");
265
    }
266
    return(ok);
267
}
268
269
/*
270
 * set hal bool
271
 */
272
void hal_bool(const char *name,uint8 b)
273
{
274
    libhal_device_set_property_bool(Globals._hal,
275
                                    Globals._udi,
276
                                    name,
277
                                    b?TRUE:FALSE,
278
                                    0);
279
}
280
281
/*
282
 * set hal int 
283
 */
284
void hal_int(const char *name,int i)
285
{
286
    libhal_device_set_property_int(Globals._hal,Globals._udi,name,i,0);
287
}
288
289
/*
290
 * set hal string 
291
 */
292
void hal_string(const char *name,const char *string)
293
{
294
    libhal_device_set_property_string(Globals._hal,Globals._udi,name,string,0);
295
}
296
297
/*
298
 * battery full signal
299
 */
300
void battery_full(void)
301
{
302
    DBusMessage *msg;
303
    
304
    if(Globals._battery._battery_full)
305
    {
306
        msg=dbus_message_new_signal(BMEPath,
307
                                    BMEDot,
308
                                    "battery_full");
309
        dbus_connection_send(Globals._dbus,msg,0);
310
        dbus_connection_flush(Globals._dbus);
311
        dbus_message_unref(msg);
312
        hal_string("battery.charge_level.capacity_state","full");
313
    }
314
    else
315
    {
316
        // this isn't called all the time, will only go from full to ok
317
        // need to adjust..
318
        hal_string("battery.charge_level.capacity_state",
319
                   Globals._battery._battery_percentage>25?"ok":"low");
320
    }
321
}
322
323
/*
324
 * charger connected
325
 */
326
void charger_connected(void)
327
{
328
    DBusMessage *msg=dbus_message_new_signal(BMEPath,
329
                                             BMEDot,
330
                                             Globals._battery._charger_connected
331
                                                 ?"charger_connected"
332
                                                 :"charger_disconnected");
333
    DBusMessage *charging;
334
    char *ptr="charger_charging_off";
335
336
    // emulates nokias bme, if charger connected report charging or full
337
    if(Globals._battery._charger_connected)
338
    {
339
        ptr=Globals._battery._battery_full?"battery_full":"charger_charging_on";
340
    }
341
    charging=dbus_message_new_signal(BMEPath,
342
                                     BMEDot,
343
                                     ptr);
344
345
    dbus_connection_send(Globals._dbus,msg,0);
346
    dbus_connection_send(Globals._dbus,charging,0);
347
    dbus_connection_flush(Globals._dbus);
348
    dbus_message_unref(msg);
349
    dbus_message_unref(charging);
350
351
    //update hal
352
    hal_bool("battery.rechargeable.is_charging",
353
             Globals._battery._charger_connected);
354
    hal_bool("battery.rechargeable.is_discharging",
355
             !Globals._battery._charger_connected);
356
}
357
358
/*
359
H
360
 * update_percentage()
361
 */
362
void update_percentage(void)
363
{
364
    hal_int("battery.charge_level.current",Globals._battery._current);
365
    hal_int("battery.reporting.current",Globals._battery._current);
366
    hal_int("battery.charge_level.percentage",
367
            Globals._battery._battery_percentage);
368
    if(Globals._battery._current>Globals._battery._last_full)
369
    {
370
        Globals._battery._last_full=Globals._battery._current;
371
        hal_int("battery.charge_level.last_full",Globals._battery._last_full);
372
        hal_int("battery.reporing.last_full",Globals._battery._last_full);
373
    }
374
}
375
376
/*
377
 * update_time()
378
 */
379
void update_time(void)
380
{
381
    DBusMessage *msg=dbus_message_new_signal(BMEPath,BMEDot,"battery_timeleft");
382
383
    if(msg)
384
    {
385
        dbus_message_append_args(msg,
386
                                 DBUS_TYPE_INT32,
387
                                 &Globals._battery._standby_time,
388
                                 DBUS_TYPE_INT32,
389
                                 &Globals._battery._bars_time);
390
        dbus_connection_send(Globals._dbus,msg,0);
391
        dbus_connection_flush(Globals._dbus);
392
        dbus_message_unref(msg);
393
    }
394
    else
395
    {
396
        print("cannot create message");
397
    }
398
}
399
400
/*
401
 * read the battery information
402
 */
403
void get_battery_info(void)
404
{
405
    uint8 ok=1;
406
    BMERequest req;
407
    struct emsg_bme_battery_info_reply info;
408
    struct emsg_bme_battery_curr_reply curr;
409
    struct emsg_bme_bulk0_reply bulk0;
410
    struct emsg_bme_bulk1_reply bulk1;
411
412
    if(ok&&connected())
413
    {
414
        ok=0;
415
        memset(&req,0,sizeof(req));
416
        req._type=EM_BATTERY_INFO_REQ;
417
        req._flags=-1;
418
        if(write(Globals._socket,&req,sizeof(req))==sizeof(req))
419
        {
420
            if(bme_read(&info,sizeof(info))==sizeof(info))
421
            {
422
                ok=1;
423
                if(Globals._battery._design!=info._capacity)
424
                {
425
                    Globals._battery._design=info._capacity;
426
                    hal_int("battery.reporting.design",
427
                            Globals._battery._design);
428
                }
429
                if(Globals._battery._temp_k!=info._temp_k)
430
                {
431
                    Globals._battery._temp_k=info._temp_k;
432
                }
433
            }
434
            else
435
            {
436
                print("Invalid info read");
437
            }
438
        }
439
        else
440
        {
441
            print("Invalid info write");
442
        }
443
    }
444
445
    if(ok&&connected())
446
    {
447
        ok=0;
448
        memset(&req,0,sizeof(req));
449
        req._type=EM_BATTERY_CURR_REQ;
450
        req._flags=-1;
451
        if(write(Globals._socket,&req,sizeof(req))==sizeof(req))
452
        {
453
            if(bme_read(&curr,sizeof(curr))==sizeof(curr))
454
            {
455
                ok=1;
456
                // prevent div by zero
457
                if(Globals._battery._design)
458
                {
459
                    // battery capacity changed.. ?
460
                    if(Globals._battery._current*100/Globals._battery._design
461
                        !=Globals._battery._battery_percentage)
462
                    {
463
                        // force update
464
                        Globals._battery._current=0;
465
                    }
466
                }
467
                if(Globals._battery._current!=curr._capacity)
468
                {
469
                    Globals._battery._current=curr._capacity;
470
                    // prevent div by zero
471
                    if(Globals._battery._design)
472
                    {
473
                        Globals._battery._battery_percentage
474
                              =Globals._battery._current*100
475
                                  /Globals._battery._design;
476
                        // update % and current..
477
                        update_percentage();
478
                    }
479
                }
480
            }
481
            else
482
            {
483
                print("Invalid curr read");
484
            }
485
        }
486
        else
487
        {
488
            print("Invalid curr write");
489
        }
490
    }
491
492
    if(ok&&connected())
493
    {
494
        ok=0;
495
        memset(&req,0,sizeof(req));
496
        req._type=BULK0_REQ;
497
        req._flags=-1;
498
        if(write(Globals._socket,&req,sizeof(req))==sizeof(req))
499
        {
500
            if(bme_read(&bulk0,sizeof(bulk0))==sizeof(bulk0))
501
            {
502
                ok=1;
503
                // update time
504
                bulk0._n_bars*=bulk0._time_per_bar;
505
                bulk0._n_bars/=60;
506
                bulk0._standby_time/=60;
507
                if(Globals._battery._standby_time!=bulk0._standby_time
508
                 ||Globals._battery._bars_time!=bulk0._n_bars)
509
                {
510
                    Globals._battery._standby_time=bulk0._standby_time;
511
                    Globals._battery._bars_time=bulk0._n_bars;
512
                    update_time();
513
                }
514
            }
515
            else
516
            {
517
                print("Invalid bulk0 read");
518
            }
519
        }
520
        else
521
        {
522
            print("Invalid bulk0 write");
523
        }
524
    }
525
    // if we failed, disconnect
526
    if(ok&&connected())
527
    {
528
        ok=0;
529
        memset(&req,0,sizeof(req));
530
        req._type=BULK1_REQ;
531
        req._flags=-1;
532
        if(write(Globals._socket,&req,sizeof(req))==sizeof(req))
533
        {
534
            if(bme_read(&bulk1,sizeof(bulk1))==sizeof(bulk1))
535
            {
536
                ok=1;
537
                // charging event?
538
                if(Globals._battery._charger_connected!=bulk1._charger_type)
539
                {
540
                    Globals._battery._charger_connected=bulk1._charger_type;
541
                    charger_connected();
542
                }
543
                // check for full battery event
544
                if(Globals._battery._battery_full!=bulk1._battery_full)
545
                {
546
                    Globals._battery._battery_full=bulk1._battery_full;
547
                    battery_full();
548
                }
549
            }
550
            else
551
            {
552
                print("Invalid bulk1 read");
553
            }
554
        }
555
        else
556
        {
557
            print("Invalid bulk1 write");
558
        }
559
    }
560
    // if we failed, disconnect
561
    if(!ok)
562
    {
563
        close_socket();
564
    }
565
}
566
567
/*
568
 * poll routine
569
 */
570
gboolean poll_socket(gpointer data)
571
{
572
    (void)data;
573
    // inc poll count
574
    ++Globals._poll_count;
575
    // valid socket?
576
    if(Globals._socket>=0)
577
    {
578
        get_battery_info();
579
    }
580
    else
581
    {
582
        // attempt to reconnect the socket if it's time
583
        if(Globals._poll_count>Globals._reconnect_count)
584
        {
585
            // reset poll count
586
            Globals._poll_count=0;
587
            connect_socket(Globals._socket_path);
588
        }
589
    }
590
    return(TRUE);
591
}
592
593
/*
594
 * claim the hal interface
595
 */
596
int claim_interface(void)
597
{
598
    int claimed;
599
    DBusError error;
600
601
    claimed=libhal_device_claim_interface(Globals._hal,
602
                                          Globals._udi,
603
                                          Globals._udi,
604
                                          "",  // shoudl be xml in here
605
                                          &error);
606
    if(claimed)
607
    {
608
        if(!libhal_device_addon_is_ready(Globals._hal,Globals._udi,&error))
609
        {
610
            print("Unable to assert ready");
611
        }
612
    }
613
    else
614
    {
615
        print("cannot claim interface");
616
    }
617
    return(claimed);
618
}
619
620
/*
621
 * main routine
622
 */
623
int main(int argc,char *argv[])
624
{
625
    DBusError error;
626
    GMainLoop *gmain=0;
627
628
    init();
629
    dbus_error_init(&error);
630
    if((Globals._dbus=dbus_bus_get(DBUS_BUS_SYSTEM,&error))!=0)
631
    {
632
        if((Globals._hal=libhal_ctx_new())!=0)
633
        {
634
            if(libhal_ctx_set_dbus_connection(Globals._hal,Globals._dbus))
635
            {
636
                if((Globals._hal=libhal_ctx_init_direct(&error))!=0)
637
                {
638
                    if(claim_interface())
639
                    {
640
                        // connect the socket
641
                        connect_socket(Globals._socket_path);
642
                        // setup main loop
643
                        dbus_connection_setup_with_g_main(Globals._dbus,0);
644
                        dbus_connection_set_exit_on_disconnect(Globals._dbus,0);
645
                        gmain=g_main_loop_new(0,FALSE);
646
                        // add poll callback
647
                        g_timeout_add(Globals._poll_interval,poll_socket,0);
648
                        // execute
649
                        g_main_loop_run(gmain);
650
                    }
651
                    else
652
                    {
653
                        print("Cannot claim interface");
654
                    }
655
                }
656
                else
657
                {
658
                    print_dbus_error("CTX init",&error);
659
                }
660
            }
661
            else
662
            {
663
                print_dbus_error("CTX dbus",&error);
664
            }
665
        }
666
        else
667
        {
668
            print_dbus_error("CTX",&error);
669
        }
670
    }
671
    else
672
    {
673
        print_dbus_error("DBUS",&error);
674
    }
675
 
676
    if(Globals._hal)
677
    {
678
        libhal_ctx_shutdown(Globals._hal,&error);
679
        libhal_ctx_free(Globals._hal);
680
    }
681
    if(Globals._dbus)
682
    {
683
        dbus_connection_unref(Globals._dbus);
684
    }
685
    return(0);
686
}