mirror of
https://github.com/majonezz/solarlife.git
synced 2026-06-02 05:24:49 +02:00
Initial entry
This commit is contained in:
@@ -0,0 +1,936 @@
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <limits.h>
|
||||
#include <errno.h>
|
||||
#include "mqtt.h"
|
||||
#include "client.h"
|
||||
#include <assert.h>
|
||||
#include "bluetooth.h"
|
||||
#include "hci.h"
|
||||
#include "hci_lib.h"
|
||||
#include "l2cap.h"
|
||||
#include "uuid.h"
|
||||
#include <sys/ioctl.h>
|
||||
#include "mainloop.h"
|
||||
#include "util.h"
|
||||
#include "att.h"
|
||||
#include "queue.h"
|
||||
#include "gatt-db.h"
|
||||
#include "gatt-client.h"
|
||||
#include "json.h"
|
||||
|
||||
#define ATT_CID 4
|
||||
#define PRLOG(...) \
|
||||
printf(__VA_ARGS__);
|
||||
|
||||
#define JSONOUTLEN 4096
|
||||
#define MQTT_SEND_PERIOD_S 10
|
||||
|
||||
static uint16_t mqtt_port = 1883;
|
||||
static uint16_t send_interval = MQTT_SEND_PERIOD_S;
|
||||
static bool verbose = false;
|
||||
static int handler_registered=0;
|
||||
struct client *cli;
|
||||
uint8_t rcv_buf[256];
|
||||
char jsonout[JSONOUTLEN];
|
||||
struct JsonVal top;
|
||||
uint8_t rcv_ptr=0, frame_len=0;
|
||||
uint16_t addr_base=0;
|
||||
char mqtt_buffer[BUFFER_SIZE_BYTES];
|
||||
client_t c;
|
||||
//int is_subscriber = 1;
|
||||
int keepalive_sec = 4;
|
||||
int nbytes;
|
||||
|
||||
struct client {
|
||||
/// socket
|
||||
int fd;
|
||||
/// pointer to a bt_att structure
|
||||
struct bt_att *att;
|
||||
/// pointer to a gatt_db structure
|
||||
struct gatt_db *db;
|
||||
/// pointer to a bt_gatt_client structure
|
||||
struct bt_gatt_client *gatt;
|
||||
/// session id
|
||||
unsigned int reliable_session_id;
|
||||
};
|
||||
|
||||
|
||||
|
||||
static void got_connection(client_t* psClnt)
|
||||
{
|
||||
uint8_t buf[128];
|
||||
require(psClnt != 0);
|
||||
if(!psClnt->sockfd) return;
|
||||
|
||||
printf("MQTT: connected to server.\n");
|
||||
nbytes = mqtt_encode_connect_msg2(buf, 0x02, keepalive_sec, (uint8_t*)"Solarlife", 9);
|
||||
|
||||
client_send(&c, (char*)buf, nbytes);
|
||||
}
|
||||
|
||||
static void lost_connection(client_t* psClnt)
|
||||
{
|
||||
require(psClnt != 0);
|
||||
printf("MQTT: disconnected from server.\n");
|
||||
mainloop_quit();
|
||||
}
|
||||
|
||||
static void got_data(client_t* psClnt, unsigned char* data, int nbytes)
|
||||
{
|
||||
require(psClnt != 0);
|
||||
|
||||
uint8_t ctrl = data[0] >> 4;
|
||||
if (ctrl == CTRL_PUBACK) {
|
||||
printf("MQTT server acknowledged data\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int16_t b2int (uint8_t *ptr) {
|
||||
|
||||
return ptr[1] | ptr[0] << 8;
|
||||
|
||||
}
|
||||
|
||||
|
||||
int b2int2 (uint8_t *ptr) {
|
||||
|
||||
return ptr[0] | ptr[1] << 8;
|
||||
|
||||
}
|
||||
|
||||
|
||||
uint16_t ModRTU_CRC(uint8_t* buf, int len)
|
||||
{
|
||||
uint16_t crc = 0xFFFF;
|
||||
|
||||
for (int pos = 0; pos < len; pos++) {
|
||||
crc ^= (uint16_t)buf[pos]; // XOR byte into least sig. byte of crc
|
||||
|
||||
for (int i = 8; i != 0; i--) { // Loop over each bit
|
||||
if ((crc & 0x0001) != 0) { // If the LSB is set
|
||||
crc >>= 1; // Shift right and XOR 0xA001
|
||||
crc ^= 0xA001;
|
||||
}
|
||||
else // Else LSB is not set
|
||||
crc >>= 1; // Just shift right
|
||||
}
|
||||
}
|
||||
// Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes)
|
||||
return crc;
|
||||
}
|
||||
|
||||
|
||||
static void write_cb(bool success, uint8_t att_ecode, void *user_data)
|
||||
{
|
||||
/*
|
||||
if (success) {
|
||||
PRLOG("\nWrite successful\n");
|
||||
} else {
|
||||
PRLOG("\nWrite failed: %s (0x%02x)\n",
|
||||
ecode_to_string(att_ecode), att_ecode);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
int send_modbus_req(uint8_t eq_id, uint8_t fct_code,uint16_t addr, uint16_t len) {
|
||||
|
||||
uint8_t buf[10];
|
||||
|
||||
|
||||
buf[0]=eq_id;
|
||||
buf[1]=fct_code;
|
||||
buf[2]=addr>>8;
|
||||
buf[3]=addr&0xff;
|
||||
buf[4]=len>>8;
|
||||
buf[5]=len&0xff;
|
||||
uint16_t crc = ModRTU_CRC(buf,6);
|
||||
buf[7]=crc>>8;
|
||||
buf[6]=crc&0xff;
|
||||
addr_base = addr;
|
||||
|
||||
rcv_ptr = 0; //reset pointer
|
||||
top = jsonCreateObject();
|
||||
|
||||
if (!bt_gatt_client_write_value(cli->gatt, 0x14, buf, 8, write_cb, NULL, NULL)) {
|
||||
printf("Failed to initiate write procedure\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
static void log_service_event(struct gatt_db_attribute *attr, const char *str)
|
||||
{
|
||||
char uuid_str[MAX_LEN_UUID_STR];
|
||||
bt_uuid_t uuid;
|
||||
uint16_t start, end;
|
||||
|
||||
gatt_db_attribute_get_service_uuid(attr, &uuid);
|
||||
bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str));
|
||||
|
||||
gatt_db_attribute_get_service_handles(attr, &start, &end);
|
||||
|
||||
PRLOG("%s - UUID: %s start: 0x%04x end: 0x%04x\n", str, uuid_str,
|
||||
start, end);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static void register_notify_cb(uint16_t att_ecode, void *user_data)
|
||||
{
|
||||
if (att_ecode) {
|
||||
PRLOG("Failed to register notify handler "
|
||||
"- error code: 0x%02x\n", att_ecode);
|
||||
return;
|
||||
}
|
||||
|
||||
PRLOG("Registered notify handler!\n");
|
||||
handler_registered = 1;
|
||||
}
|
||||
|
||||
void parse_val(uint8_t *buf, uint16_t addr) {
|
||||
|
||||
//printf("Parsing value at address %04x\n",addr);
|
||||
uint8_t ptr = addr*2+3;
|
||||
addr+=addr_base;
|
||||
|
||||
switch (addr) {
|
||||
|
||||
case 0x3000:
|
||||
JsonVal_objectAddNumber(&top, "PV_rated_voltage", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3001:
|
||||
JsonVal_objectAddNumber(&top, "PV_rated_current", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3002:
|
||||
JsonVal_objectAddNumber(&top, "PV_rated_power_l", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3003:
|
||||
JsonVal_objectAddNumber(&top, "PV_rated_power_h", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
|
||||
case 0x3004:
|
||||
JsonVal_objectAddNumber(&top, "battery_rated_voltage", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3005:
|
||||
JsonVal_objectAddNumber(&top, "battery_rated_current", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3006:
|
||||
JsonVal_objectAddNumber(&top, "battery_rated_power_l", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3007:
|
||||
JsonVal_objectAddNumber(&top, "battery_rated_power_h", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
|
||||
case 0x3008:
|
||||
JsonVal_objectAddNumber(&top, "load_rated_voltage", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3009:
|
||||
JsonVal_objectAddNumber(&top, "load_rated_current", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x300a:
|
||||
JsonVal_objectAddNumber(&top, "load_rated_power_l", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x300b:
|
||||
JsonVal_objectAddNumber(&top, "load_rated_power_h", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
|
||||
|
||||
case 0x3030:
|
||||
JsonVal_objectAddNumber(&top, "slave_id", (int)b2int(buf+ptr));
|
||||
break;
|
||||
case 0x3031:
|
||||
JsonVal_objectAddNumber(&top, "running_days", (int)b2int(buf+ptr));
|
||||
break;
|
||||
case 0x3032:
|
||||
JsonVal_objectAddNumber(&top, "sys_voltage", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3033:
|
||||
JsonVal_objectAddNumber(&top, "battery_status", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x3034:
|
||||
JsonVal_objectAddNumber(&top, "charge_status", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x3035:
|
||||
JsonVal_objectAddNumber(&top, "discharge_status", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x3036:
|
||||
JsonVal_objectAddNumber(&top, "env_temperature", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3037:
|
||||
JsonVal_objectAddNumber(&top, "sys_temperature", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3038:
|
||||
JsonVal_objectAddNumber(&top, "undervoltage_times", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x3039:
|
||||
JsonVal_objectAddNumber(&top, "fullycharged_times", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x303a:
|
||||
JsonVal_objectAddNumber(&top, "overvoltage_prot_times", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x303b:
|
||||
JsonVal_objectAddNumber(&top, "overcurrent_prot_times", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x303c:
|
||||
JsonVal_objectAddNumber(&top, "shortcircuit_prot_times", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x303d:
|
||||
JsonVal_objectAddNumber(&top, "opencircuit_prot_times", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x303e:
|
||||
JsonVal_objectAddNumber(&top, "hw_prot_times", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x303f:
|
||||
JsonVal_objectAddNumber(&top, "charge_overtemp_prot_times", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x3040:
|
||||
JsonVal_objectAddNumber(&top, "discharge_overtemp_prot_times", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x3045:
|
||||
JsonVal_objectAddNumber(&top, "battery_remaining_capacity", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x3046:
|
||||
JsonVal_objectAddNumber(&top, "battery_voltage", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3047:
|
||||
JsonVal_objectAddNumber(&top, "battery_current", (float)b2int(buf+ptr)/100);
|
||||
//JsonVal_objectAddNumber(&top, "battery_current", b2int(buf+ptr)/100);
|
||||
//printf("current: %d\n",b2int(buf+ptr));
|
||||
|
||||
break;
|
||||
case 0x3048:
|
||||
JsonVal_objectAddNumber(&top, "battery_power_lo", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3049:
|
||||
JsonVal_objectAddNumber(&top, "battery_power_hi", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x304a:
|
||||
JsonVal_objectAddNumber(&top, "load_voltage", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x304b:
|
||||
JsonVal_objectAddNumber(&top, "load_current", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x304c:
|
||||
JsonVal_objectAddNumber(&top, "load_power_l", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x304d:
|
||||
JsonVal_objectAddNumber(&top, "load_power_h", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x304e:
|
||||
JsonVal_objectAddNumber(&top, "solar_voltage", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x304f:
|
||||
JsonVal_objectAddNumber(&top, "solar_current", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3050:
|
||||
JsonVal_objectAddNumber(&top, "solar_power_l", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3051:
|
||||
JsonVal_objectAddNumber(&top, "solar_power_h", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3052:
|
||||
JsonVal_objectAddNumber(&top, "daily_production", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3053:
|
||||
JsonVal_objectAddNumber(&top, "total_production_l", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3054:
|
||||
JsonVal_objectAddNumber(&top, "total_production_h", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3055:
|
||||
JsonVal_objectAddNumber(&top, "daily_consumption", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3056:
|
||||
JsonVal_objectAddNumber(&top, "total_consumption_l", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3057:
|
||||
JsonVal_objectAddNumber(&top, "total_consumption_h", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3058:
|
||||
JsonVal_objectAddNumber(&top, "lighttime_daily", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x305d:
|
||||
JsonVal_objectAddNumber(&top, "monthly_production_l", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x305e:
|
||||
JsonVal_objectAddNumber(&top, "monthly_production_h", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x305f:
|
||||
JsonVal_objectAddNumber(&top, "yearly_production_l", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x3060:
|
||||
JsonVal_objectAddNumber(&top, "yearly_production_h", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
|
||||
case 0x9017:
|
||||
JsonVal_objectAddNumber(&top, "rtc_second", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x9018:
|
||||
JsonVal_objectAddNumber(&top, "rtc_minute", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x9019:
|
||||
JsonVal_objectAddNumber(&top, "rtc_hour", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x901a:
|
||||
JsonVal_objectAddNumber(&top, "rtc_day", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x901b:
|
||||
JsonVal_objectAddNumber(&top, "rtc_month", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x901c:
|
||||
JsonVal_objectAddNumber(&top, "rtc_year", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x901d:
|
||||
JsonVal_objectAddNumber(&top, "baud_rate", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x901f:
|
||||
JsonVal_objectAddNumber(&top, "password", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x9021:
|
||||
JsonVal_objectAddNumber(&top, "battery_type", b2int(buf+ptr));
|
||||
break;
|
||||
case 0x9022:
|
||||
JsonVal_objectAddNumber(&top, "lvp_voltage", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x9023:
|
||||
JsonVal_objectAddNumber(&top, "lvr_voltage", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x9024:
|
||||
JsonVal_objectAddNumber(&top, "boost_voltage", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x9025:
|
||||
JsonVal_objectAddNumber(&top, "equalizing_voltage", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
case 0x9026:
|
||||
JsonVal_objectAddNumber(&top, "floating_voltage", (float)b2int(buf+ptr)/100);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void notify_cb(uint16_t value_handle, const uint8_t *value,
|
||||
uint16_t length, void *user_data)
|
||||
{
|
||||
|
||||
memcpy(rcv_buf+rcv_ptr, value, length);
|
||||
rcv_ptr+=length;
|
||||
|
||||
if (rcv_ptr==rcv_buf[2]+5) {
|
||||
|
||||
uint16_t crc_r = b2int2(rcv_buf+rcv_ptr-2);
|
||||
uint16_t crc = ModRTU_CRC(rcv_buf, rcv_ptr-2);
|
||||
if((crc==crc_r) && addr_base) { //CRC check passed
|
||||
for (int ii = 0; ii<(rcv_buf[2]-2)/2; ii++) {
|
||||
parse_val(rcv_buf,ii);
|
||||
|
||||
}
|
||||
time_t now;
|
||||
time(&now);
|
||||
char buf[sizeof "2011-10-08T07:07:09Z"];
|
||||
strftime(buf, sizeof buf, "%FT%TZ", gmtime(&now));
|
||||
JsonVal_objectAddString(&top, "timestamp", buf);
|
||||
JsonVal_writeString(&top, jsonout, JSONOUTLEN);
|
||||
puts(jsonout);
|
||||
char buf2[8000];
|
||||
nbytes = mqtt_encode_publish_msg((uint8_t*)buf2, (uint8_t*)"Solarlife",9 , 1, 10, (uint8_t*)jsonout, strlen(jsonout));
|
||||
if(c.sockfd) {
|
||||
client_send(&c, buf2, nbytes);
|
||||
client_poll(&c, 1000000);
|
||||
}
|
||||
JsonVal_destroy(&top);
|
||||
//if(addr_base<0x9000) {
|
||||
// send_modbus_req(0x01, 0x03, 0x9000, 0x40);
|
||||
// addr_base = 0;
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static void ready_cb(bool success, uint8_t att_ecode, void *user_data)
|
||||
{
|
||||
struct client *cli = user_data;
|
||||
|
||||
if (!success) {
|
||||
PRLOG("GATT discovery procedures failed - error code: 0x%02x\n",
|
||||
att_ecode);
|
||||
return;
|
||||
}
|
||||
|
||||
PRLOG("GATT discovery procedures complete\n");
|
||||
|
||||
unsigned char buf[] = {01,00};
|
||||
if (!bt_gatt_client_write_value(cli->gatt, 0x12, buf, sizeof(buf),
|
||||
write_cb,
|
||||
NULL, NULL))
|
||||
printf("Failed to initiate write procedure\n");
|
||||
|
||||
unsigned int id = bt_gatt_client_register_notify(cli->gatt, 0x11,
|
||||
register_notify_cb,
|
||||
notify_cb, NULL, NULL);
|
||||
if (!id) {
|
||||
printf("Failed to register notify handler\n");
|
||||
}
|
||||
|
||||
PRLOG("Registering notify handler with id: %u\n", id);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void service_removed_cb(struct gatt_db_attribute *attr, void *user_data)
|
||||
{
|
||||
log_service_event(attr, "Service Removed");
|
||||
}
|
||||
|
||||
|
||||
static void service_added_cb(struct gatt_db_attribute *attr, void *user_data)
|
||||
{
|
||||
log_service_event(attr, "Service Added");
|
||||
}
|
||||
|
||||
|
||||
static void att_disconnect_cb(int err, void *user_data)
|
||||
{
|
||||
printf("Device disconnected: %s\n", strerror(err));
|
||||
mainloop_quit();
|
||||
}
|
||||
|
||||
void send_mqtt_ping(void) {
|
||||
|
||||
unsigned char buf[100];
|
||||
nbytes = mqtt_encode_ping_msg(buf);
|
||||
if ((nbytes != 0) && (c.sockfd))
|
||||
{
|
||||
client_send(&c, (char*)buf, nbytes);
|
||||
client_poll(&c, 0);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
static void signal_cb(int signum, void *user_data)
|
||||
{
|
||||
|
||||
static int sec_counter;
|
||||
switch (signum) {
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
mainloop_quit();
|
||||
break;
|
||||
case SIGALRM:
|
||||
{
|
||||
//if(addr_base) send_modbus_req(0x01, 0x03, 0x9000, 0x40);
|
||||
//else {
|
||||
// send_modbus_req(0x01, 0x04, 0x3000, 0x7c);
|
||||
// addr_base=0;
|
||||
//}
|
||||
//uint16_t len = form_modbus_msg(buff,0x01, 0x03, 0x9000, 0x40);
|
||||
//send_modbus_req(0x01, 0x04, 0x3000, 0x7c);
|
||||
//send_modbus_req(0x01, 0x04, 0x3047, 0x20);
|
||||
send_mqtt_ping();
|
||||
if(handler_registered) {
|
||||
if (++sec_counter>send_interval) {
|
||||
send_modbus_req(0x01, 0x04, 0x3000, 0x7c);
|
||||
sec_counter=0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
alarm(1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void client_destroy(struct client *cli)
|
||||
{
|
||||
bt_gatt_client_unref(cli->gatt);
|
||||
bt_att_unref(cli->att);
|
||||
free(cli);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static struct client *client_create(int fd, uint16_t mtu)
|
||||
{
|
||||
struct client *cli;
|
||||
|
||||
cli = new0(struct client, 1);
|
||||
if (!cli) {
|
||||
fprintf(stderr, "Failed to allocate memory for client\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cli->att = bt_att_new(fd, false);
|
||||
if (!cli->att) {
|
||||
fprintf(stderr, "Failed to initialze ATT transport layer\n");
|
||||
bt_att_unref(cli->att);
|
||||
free(cli);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!bt_att_set_close_on_unref(cli->att, true)) {
|
||||
fprintf(stderr, "Failed to set up ATT transport layer\n");
|
||||
bt_att_unref(cli->att);
|
||||
free(cli);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!bt_att_register_disconnect(cli->att, att_disconnect_cb, NULL,
|
||||
NULL)) {
|
||||
fprintf(stderr, "Failed to set ATT disconnect handler\n");
|
||||
bt_att_unref(cli->att);
|
||||
free(cli);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cli->fd = fd;
|
||||
cli->db = gatt_db_new();
|
||||
if (!cli->db) {
|
||||
fprintf(stderr, "Failed to create GATT database\n");
|
||||
bt_att_unref(cli->att);
|
||||
free(cli);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cli->gatt = bt_gatt_client_new(cli->db, cli->att, mtu);
|
||||
if (!cli->gatt) {
|
||||
fprintf(stderr, "Failed to create GATT client\n");
|
||||
gatt_db_unref(cli->db);
|
||||
bt_att_unref(cli->att);
|
||||
free(cli);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gatt_db_register(cli->db, service_added_cb, service_removed_cb,
|
||||
NULL, NULL);
|
||||
|
||||
bt_gatt_client_set_ready_handler(cli->gatt, ready_cb, cli, NULL);
|
||||
|
||||
/* bt_gatt_client already holds a reference */
|
||||
gatt_db_unref(cli->db);
|
||||
|
||||
return cli;
|
||||
}
|
||||
|
||||
|
||||
static int l2cap_le_att_connect(bdaddr_t *src, bdaddr_t *dst, uint8_t dst_type,
|
||||
int sec)
|
||||
{
|
||||
int sock;
|
||||
struct sockaddr_l2 srcaddr, dstaddr;
|
||||
struct bt_security btsec;
|
||||
|
||||
|
||||
sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
|
||||
if (sock < 0) {
|
||||
perror("Failed to create L2CAP socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set up source address */
|
||||
memset(&srcaddr, 0, sizeof(srcaddr));
|
||||
srcaddr.l2_family = AF_BLUETOOTH;
|
||||
srcaddr.l2_cid = htobs(ATT_CID);
|
||||
srcaddr.l2_bdaddr_type = 0;
|
||||
bacpy(&srcaddr.l2_bdaddr, src);
|
||||
|
||||
if (bind(sock, (struct sockaddr *)&srcaddr, sizeof(srcaddr)) < 0) {
|
||||
perror("Failed to bind L2CAP socket");
|
||||
close(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set the security level */
|
||||
memset(&btsec, 0, sizeof(btsec));
|
||||
btsec.level = sec;
|
||||
if (setsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &btsec,
|
||||
sizeof(btsec)) != 0) {
|
||||
fprintf(stderr, "Failed to set L2CAP security level\n");
|
||||
close(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set up destination address */
|
||||
memset(&dstaddr, 0, sizeof(dstaddr));
|
||||
dstaddr.l2_family = AF_BLUETOOTH;
|
||||
dstaddr.l2_cid = htobs(ATT_CID);
|
||||
dstaddr.l2_bdaddr_type = dst_type;
|
||||
bacpy(&dstaddr.l2_bdaddr, dst);
|
||||
|
||||
printf("Connecting to BT device...");
|
||||
fflush(stdout);
|
||||
|
||||
if (connect(sock, (struct sockaddr *) &dstaddr, sizeof(dstaddr)) < 0) {
|
||||
perror(" Failed to connect to BT device");
|
||||
close(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf(" Done\n");
|
||||
|
||||
return sock;
|
||||
}
|
||||
|
||||
void inthandler(int dummy)
|
||||
{
|
||||
(void)dummy;
|
||||
|
||||
uint8_t buf[128];
|
||||
int nbytes = mqtt_encode_disconnect_msg(buf);
|
||||
if (nbytes != 0)
|
||||
{
|
||||
client_send(&c, (char*)buf, nbytes);
|
||||
}
|
||||
|
||||
client_poll(&c, 10000000);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void usage(char *argv[])
|
||||
{
|
||||
printf("%s\n",argv[0]);
|
||||
printf("Usage:\n\t%s [options]\n",argv[0]);
|
||||
|
||||
printf("Options:\n"
|
||||
"\t-i, --index <id>\t\tSpecify adapter index, e.g. hci0\n"
|
||||
"\t-d, --dest <addr>\t\tSpecify the destination mac address of the Solarlife device\n"
|
||||
"\t-a, --host <ipaddr>\t\tSpecify the MQTT broker address\n"
|
||||
"\t-p, --port \t\t\tMQTT broker port, default: %d\n"
|
||||
"\t-t, --interval \t\t\tSet data sending interval in seconds, default: %d s\n"
|
||||
"\t-v, --verbose\t\t\tEnable extra logging\n"
|
||||
"\t-h, --help\t\t\tDisplay help\n",mqtt_port, send_interval);
|
||||
|
||||
printf("Example:\n"
|
||||
"%s -d C4:BE:84:70:29:04 -a test.mosquitto.org -t 10\n",argv[0]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static struct option main_options[] = {
|
||||
{ "index", 1, 0, 'i' },
|
||||
{ "dest", 1, 0, 'd' },
|
||||
{ "host", 1, 0, 'a' },
|
||||
{ "port", 1, 0, 'p' },
|
||||
{ "interval", 1, 0, 't' },
|
||||
{ "verbose", 0, 0, 'v' },
|
||||
{ "help", 0, 0, 'h' },
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
int opt;
|
||||
int sec = BT_SECURITY_LOW;
|
||||
uint16_t mtu = 0;
|
||||
uint8_t dst_type = BDADDR_LE_PUBLIC;
|
||||
bool dst_addr_given = false;
|
||||
bdaddr_t src_addr, dst_addr;
|
||||
int dev_id = -1;
|
||||
int fd;
|
||||
sigset_t mask;
|
||||
char mqtt_host[100];
|
||||
|
||||
|
||||
//str2ba("04:7f:0e:54:61:0c", &dst_addr);
|
||||
|
||||
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "+hva:t:d:i:p:",
|
||||
main_options, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 'h':
|
||||
usage(argv);
|
||||
return EXIT_SUCCESS;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case 'a':
|
||||
strncpy(mqtt_host,optarg,sizeof(mqtt_host));
|
||||
break;
|
||||
|
||||
case 'p': {
|
||||
int arg;
|
||||
|
||||
arg = atoi(optarg);
|
||||
if (arg <= 0) {
|
||||
fprintf(stderr, "Invalid MQTT port: %d\n", arg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (arg > UINT16_MAX) {
|
||||
fprintf(stderr, "MQTT port too large: %d\n", arg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
mqtt_port = (uint16_t)arg;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 't': {
|
||||
int arg;
|
||||
|
||||
arg = atoi(optarg);
|
||||
if (arg <= 0) {
|
||||
fprintf(stderr, "Invalid interval: %d s\n", arg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (arg > UINT16_MAX) {
|
||||
fprintf(stderr, "Interval too large: %d s\n", arg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (arg < 1) {
|
||||
fprintf(stderr, "Interval too small: %d s\n", arg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
send_interval = (uint16_t)arg;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'd':
|
||||
if (str2ba(optarg, &dst_addr) < 0) {
|
||||
fprintf(stderr, "Invalid Solarlife address: %s\n",
|
||||
optarg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
dst_addr_given = true;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
dev_id = hci_devid(optarg);
|
||||
if (dev_id < 0) {
|
||||
perror("Invalid adapter");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
//fprintf(stderr, "Invalid option: %c\n", opt);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!argc) {
|
||||
usage(argv);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
optind = 0;
|
||||
|
||||
if (argc) {
|
||||
usage(argv);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (dev_id == -1)
|
||||
bacpy(&src_addr, BDADDR_ANY);
|
||||
else if (hci_devba(dev_id, &src_addr) < 0) {
|
||||
perror("Adapter not available");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!dst_addr_given) {
|
||||
fprintf(stderr, "Destination BT mac address required! Use -h option to print usage.\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
||||
/* create the mainloop resources */
|
||||
|
||||
|
||||
mainloop_init();
|
||||
|
||||
fd = l2cap_le_att_connect(&src_addr, &dst_addr, dst_type, sec);
|
||||
if (fd < 0)
|
||||
return EXIT_FAILURE;
|
||||
|
||||
|
||||
cli = client_create(fd, mtu);
|
||||
if (!cli) {
|
||||
close(fd);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if(strlen(mqtt_host)) {
|
||||
client_init(&c, mqtt_host, mqtt_port, mqtt_buffer, BUFFER_SIZE_BYTES);
|
||||
assert(client_set_callback(&c, CB_RECEIVED_DATA, got_data) == 1);
|
||||
assert(client_set_callback(&c, CB_ON_CONNECTION, got_connection) == 1);
|
||||
assert(client_set_callback(&c, CB_ON_DISCONNECT, lost_connection) == 1);
|
||||
signal(SIGINT, inthandler);
|
||||
client_connect(&c);
|
||||
} else {
|
||||
printf("MQTT Broker address not given, will not send data.\n");
|
||||
}
|
||||
alarm(1);
|
||||
|
||||
|
||||
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGINT);
|
||||
sigaddset(&mask, SIGTERM);
|
||||
sigaddset(&mask, SIGALRM);
|
||||
|
||||
|
||||
/* add handler for process interrupted (SIGINT) or terminated (SIGTERM)*/
|
||||
mainloop_set_signal(&mask, signal_cb, NULL, NULL);
|
||||
|
||||
mainloop_run();
|
||||
|
||||
printf("\n\nShutting down...\n");
|
||||
if(c.sockfd) client_disconnect(&c);
|
||||
client_destroy(cli);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user