IoT sensor/controller using STM32, W5500 ethernet, MQTT

Dependencies:   mbed WIZnet_Library Watchdog DHT MQTT DS1820

main.cpp

Committer:
Geekshow
Date:
2020-03-04
Revision:
14:0a3c670b3862
Parent:
12:bcb38c1af703

File content as of revision 14:0a3c670b3862:

#include "mbed.h"
#include "Watchdog.h"
//#include "rtos.h"
//#include "pins.h"
#include "WIZnetInterface.h"
#include "MQTTSocket.h"
#include "MQTTClient.h"

#define VERSION "v12"
// ========== PIN DEFINITIONS ============
// TODO move pin definitions into separate file
#define LED_GREEN PA_5
#define LED_ORANGE PA_1 // Don't use! Shared with D3
#define BUTTON PC_9

#define A_0 PC_0  // Analogue Input 0
#define A_1 PC_1  // Analogue Input 1
#define A_2 PC_2  // Analogue Input 2
#define A_3 PC_3  // Analogue Input 3
#define A_4 PC_4  // Analogue Input 4
#define A_5 PC_5  // Analogue Input 5

#define D_0 PA_3  // digital output D0
#define D_1 PA_2  // digital output D1
#define D_2 PA_0  // digital output D2
#define D_3 PA_1  // digital output D3
#define D_4 PB_5  // digital output D4
#define D_5 PB_6  // digital output D5
#define D_6 PA_8  // digital output D6
#define D_7 PA_9  // digital output D7
#define D_8 PA_10  // digital output D8
#define D_9 PB_7  // digital output D9
#define D_10 PA_4  // digital output D10 - SPI1 SS
#define D_11 PA_4  // digital output D11 - SPI1 MOSI
#define D_12 PA_4  // digital output D12 - SPI1 MISO
//#define D_13 PA_4  // digital output D13 - SPI1 CLK - GREEN LED
#define D_14 PB_8  // digital output D14
#define D_35 PC_6  // pin 13 on Extension
#define D_36 PC_7  // pin 14 on Extension
#define D_37 PC_8  // pin 15 on Extension (last)
// ================= *************** ==================
#define USART3_TX   PC_10 // D26 - pin 4 on Extension
// ================= *************** ==================
// serial output? for uLCD
// ================= *************** ==================
// sensor inputs
#include "DS1820.h"
#define MAX_PROBES      4
#define ONEWIRE_PIN         PB_11  // D30 pin 6 on UEXT // pin 8 on Extension
DS1820* probe[MAX_PROBES];
#include "DHT.h"
#define DHT_PIN            PB_10  // D29 pin 5 on UEXT // pin 7 on Extension
// ================= *************** ==================
#define NODE_NAME "controller03" // TODO just define node number

#define NUM_OUTPUTS 8
DigitalOut outputs[NUM_OUTPUTS] = {D_0, D_1, D_2, D_3, D_4, D_5, D_6, D_7};
#define NUM_INPUTS 6
DigitalIn inputs[NUM_INPUTS] = {PC_0, PC_1, PC_2, PC_3, PC_4, PC_5};
bool input_state[NUM_INPUTS];

Serial pc(USART3_TX, NC); // serial debug output on D26 (pin 4 of Extension)
//Serial pc(PA_9, NC); // serial debug output on D7
//Serial xxxxxx  // find a serial port for Amp/uLCD connection

DigitalIn button(BUTTON);
DigitalOut led(LED_GREEN);

DHT dht0(DHT_PIN, DHT22);
float temp[1];
float humidity[1];

Watchdog wd;
Ticker tick_30sec;
Ticker tick_5sec;
Ticker tick_1sec;
Ticker tick_500ms;

bool flag_publish;
bool flag_read_dht;
bool flag_read_ds18b20;

typedef MQTT::Client<MQTTSocket,Countdown> MClient;

const char* ONOFF[] = {"ON", "OFF"};
const char* OPENCLOSED[] = {"CLOSED", "OPEN"};
enum IO_STATE{IO_ON, IO_OFF};

uint8_t mac_addr[6]={0x00, 0x00, 0x00, 0xBE, 0xEF, 0x03}; // TODO make last byte dynamic
const char* mqtt_broker = "192.168.10.4";
//const char* mqtt_broker = "192.168.1.99";
const int mqtt_port = 1883;
unsigned long uptime_sec = 0;
int connected = -1;



void on_control_cmd(const char* topic, const char* message)
{
    int new_state = 0;
    pc.printf("Received CMD %s %s\r\n", topic, message);
    // find out command first
    if(strcmp(message, "ON") == 0) {
        pc.printf("ON value requested!\r\n");
        new_state = IO_ON;
    }
    else if(strcmp(message, "OFF") == 0) {
        pc.printf("OFF value requested!\r\n");
        new_state = IO_OFF;
    }
    else {
        pc.printf("Unknown command value specified!\r\n"); // TODO return current value on no message
        return;
    }
    // are we updating an output?
    if(strncmp(topic, "output", 6) == 0) {
        // find out which output to apply it to 
        int output_num = int(topic[6])-48;
        if(output_num >= NUM_OUTPUTS) {
            pc.printf("ERROR: unknown output num %d\r\n", output_num);
        }
        else {
            // turn something on/off!
            pc.printf("Output: %d updated to %s\r\n", output_num, ONOFF[new_state]);
            outputs[output_num] = new_state;
            flag_publish = 1;  // workaround for below
//            publish_value(client, topic, ONOFF[new_state], false);  // needs to access client :-/
        }
    }
    else {
        pc.printf("ERROR: Couldn't parse topic: %s\r\n", topic);
    }
}

int publish(MClient& client, const char* msg_type, const char* point, 
                    const char* payload = NULL, size_t payload_len = 0, 
                    bool retain = false, MQTT::QoS qos = MQTT::QOS1){
    char topic[64];
    sprintf(topic, "%s/" NODE_NAME "/%s", msg_type, point);
    int ret = client.publish(topic, (void*)payload, payload_len, qos, retain);
    if(ret == -1) {
        pc.printf("ERROR during client.publish() = %d\r\n",ret);
    }
    return ret;
}


void messageArrived(MQTT::MessageData& md)
{
    // MQTT callback function
    MQTT::Message &message = md.message;

    // copy message payload into local char array IMPROVE ME!
    char* payload = new char[message.payloadlen+1];
    if(!payload)  // will this ever happen?
        return;
    memcpy(payload, message.payload, message.payloadlen);
    payload[message.payloadlen]='\0';
    
    // copy topic payload into local char array IMPROVE ME!
    char* topic = new char[md.topicName.lenstring.len+1];
    if(!topic){  // will this ever happen?
        delete[] payload;
        return;
    }
    memcpy(topic, md.topicName.lenstring.data, md.topicName.lenstring.len);
    topic[md.topicName.lenstring.len]='\0';
    
    pc.printf("Rcvd: %s : %s\r\n", topic, payload);
    
    // find first delimiter in topic string
    char *topics = strtok (topic,"/");
    for (int tok=0; tok<2 && topics != NULL; tok++)  // WARNING! hard coded 2 layer topic!
    {
//        pc.printf ("Topics %d: %s\r\n",tok, topics);
        topics = strtok (NULL, "/");
    }
    on_control_cmd(topics, payload);
    delete[] topic;
    delete[] payload;
}


int publish_value(MClient &client, const char *topic, const char *buf, bool retain = false)
{
    return publish(client, "stat", topic, buf, strlen(buf), retain);
}


void publish_outputs(MClient &client) {
    for(int i=0; i<NUM_OUTPUTS; i++) {
        bool output_state = outputs[i];
        char topic[] = "outputx";
        topic[6] = i+48;
        pc.printf("Output: %s is %s\r\n", topic, ONOFF[output_state]);
        connected = publish_value(client, topic, ONOFF[output_state], false);
    }
}

void publish_inputs(MClient &client) {
    for(int i=0; i<NUM_INPUTS; i++) {
        char topic_str[8]; // long enough string for inputx
        sprintf(topic_str, "input%d", i);
        publish_value(client,topic_str,OPENCLOSED[input_state[i]], false);
    }
}


void publish_info(MClient &client) {
    // uptime
    pc.printf("Uptime %d\r\n", uptime_sec);
    char uptime_sec_str[12]; // long enough string for a long int
    sprintf(uptime_sec_str, "%d", uptime_sec);
    publish_value(client,"uptime",uptime_sec_str, false);
    // alive
    publish_value(client, "alive","ON", false);
}


void read_inputs(MClient &client) {
    for(int i=0; i<NUM_INPUTS; i++) {
        bool old_state = input_state[i];    // save old state
        input_state[i] = inputs[i];         // read new value
//        pc.printf("Input %d is %d\r\n", i, input_state[i]);
        if(input_state[i] != old_state) {
            // input has changed state
            pc.printf("Input %d changed to %s\r\n", i, OPENCLOSED[input_state[i]]);
            char topic_str[8]; // long enough string for inputx
            sprintf(topic_str, "input%d", i);
            publish_value(client,topic_str,OPENCLOSED[input_state[i]], false);
        }
    }
}


void read_dht(MClient &client) {
    int error = dht0.readData();
    if (0 == error) {
        temp[0]      = dht0.ReadTemperature(CELCIUS);
        humidity[0]  = dht0.ReadHumidity();
        pc.printf("Temperature: %3.1f, Humidity: %3.1f\n", temp[0], humidity[0]);
    } else {
        pc.printf("DHT read error: %d\n", error);
        return;
    }
    // convert to string and publish
    char temp_str[6];
    sprintf(temp_str, "%3.1f", temp[0]);
    publish_value(client,"temp0",temp_str, false);
    char humidity_str[6];
    sprintf(humidity_str, "%3.1f", humidity[0]);
    publish_value(client,"humidity0",humidity_str, false);
}
    
    
void read_ds18b20(MClient &client, int num_ds18b20) {
    // Announce num of DS18B20 found
    char temp_str[6];
    char topic_str[6];
    sprintf(temp_str, "%d", num_ds18b20);
    publish_value(client,"num_ds18b20",temp_str, false);
    if(num_ds18b20 > 0) {
        //Start temperature conversion, wait until ready
        probe[0]->convertTemperature(true, DS1820::all_devices);     
        for (int i = 0; i<num_ds18b20; i++) {
            float temp = probe[i]->temperature();
            pc.printf("Device %d returns %3.3foC\r\n", i, temp);
            // convert to string and publish
            sprintf(temp_str, "%3.3f", temp);
            sprintf(topic_str, "probetemp%d", i);
            publish_value(client,topic_str,temp_str, false);
        }
    }
}


int networking_init(MQTTSocket &sock, MClient &client, WIZnetInterface &wiz) {
    int ret = 0;
    pc.printf("\n\nNode: %s\r\n", NODE_NAME);
    pc.printf("%s attempting ethernet connection...\r\n", NODE_NAME);
    wiz.init(mac_addr); // resets the w5500
    if (wiz.connect() == (-1)) {
        pc.printf("Error getting DHCP address!!\r\n");
    }
    
    pc.printf("IP: %s\r\n", wiz.getIPAddress());
        
    ret = sock.connect((char*)mqtt_broker,mqtt_port);
    if(ret != 0){
        pc.printf("failed to connect to TCP server\r\n");
        return 1;
    }
    pc.printf("sock.connect()=%d\r\n",ret);
        
    if(client.connect() != 0){
        pc.printf("MQTT connect failed\r\n");
        return -1;
    }
    pc.printf("client.connect()=%d\r\n",ret);
    
    ret = client.subscribe("cmnd/" NODE_NAME "/+", MQTT::QOS1, messageArrived);    
    pc.printf("client.subscribe()=%d\r\n", ret);
    // TODO add client ID when subscribing

    // Node online message
    publish_value(client, "alive","ON", false);
    publish_value(client, "version", VERSION, true);
    publish_value(client, "IPAddress", wiz.getIPAddress(), true);
    pc.printf("Initialization done.\r\n");
    
    return 0;
} 

void every_30sec() {
    // no waits or blocking routines here please!
    flag_read_dht = 1;
    flag_read_ds18b20 = 1;
}

void every_5sec() {
    // no waits or blocking routines here please!
    flag_publish = 1;
}

void every_second() {
    // no waits or blocking routines here please!
    uptime_sec++;
    if(connected == 0) {
        led = !led;
    }
    wd.Service();       // kick the dog before the timeout
}

void every_500ms() {
    // no waits or blocking routines here please!
    if(connected != 0) {
        led = !led;
    }
}

int main()
{
    wd.Configure(20.0);
//    WIZnetInterface wiz(PA_7, PA_6, PA_5, PA_4, NC); // SPI1 with no reset
    WIZnetInterface wiz(PB_15, PB_14, PB_13, PB_12, PC_6); // SPI2 with D35 reset
    MQTTSocket sock;
    MClient client(sock);
    
    tick_500ms.attach(&every_500ms, 0.5);
    tick_1sec.attach(&every_second, 1.0);
    tick_5sec.attach(&every_5sec, 5.1);
    tick_30sec.attach(&every_30sec, 29.5);
    
    //pulse all outputs
    for(int i=0; i<NUM_OUTPUTS; i++) {
        outputs[i] = IO_OFF;
        wait(0.2);
    }
    
    // pull high all inputs
    for(int i=0; i<NUM_INPUTS; i++) {
        inputs[i].mode(PullUp);
    }

    pc.printf("\n\nNode: %s\r\n", NODE_NAME);
    
    wd.Service();       // kick the dog before the timeout
    connected = networking_init(sock, client, wiz);

    // Initialize DS18B20 probe array to DS1820 objects
    int num_ds18b20 = 0;
    while(DS1820::unassignedProbe(ONEWIRE_PIN)) {
        probe[num_ds18b20] = new DS1820(ONEWIRE_PIN);
        num_ds18b20++;
        if (num_ds18b20 == MAX_PROBES)
            break;
    }
    pc.printf("DS18B20: Found %d device(s)\r\n", num_ds18b20);
    
    while(1) {
        read_inputs(client);
        
        if(connected != 0) {
            pc.printf("Restarting network....\r\n");
            connected = networking_init(sock, client, wiz);
        }
        else {
            // we're connected, do stuff!
            if(flag_publish) {
                publish_outputs(client);
                publish_inputs(client);
                publish_info(client);
                flag_publish = 0;
            }
            else if(flag_read_dht) {
                read_dht(client);
                flag_read_dht = 0;
            }
            else if(flag_read_ds18b20) {
                read_ds18b20(client, num_ds18b20);
                flag_read_ds18b20 = 0;
            }
        }
        
        client.yield(50);  // pause a while, yawn......
    }
}