Skip to content

Instantly share code, notes, and snippets.

@m2mIO-gister
Last active July 23, 2018 18:34
Show Gist options
  • Save m2mIO-gister/5155641 to your computer and use it in GitHub Desktop.
Save m2mIO-gister/5155641 to your computer and use it in GitHub Desktop.
2lemetry RTX Application
/****************************************************************************
* Program/file: Main.c
*
* Copyright (C) by RTX A/S, Denmark.
* These computer program listings and specifications, are the property of
* RTX A/S, Denmark and shall not be reproduced or copied or used in
* whole or in part without written permission from RTX A/S, Denmark.
*
* DESCRIPTION: Co-Located Application (COLA).
*
****************************************************************************/
/****************************************************************************
* PVCS info
*****************************************************************************
$Author: lka $
$Date: 11 Dec 2012 21:01:46 $
$Revision: 1.3 $
$Modtime: 11 Dec 2012 21:01:16 $
$Archive: J:/sw/Projects/Amelie/COLApps/Apps/2lemetry/vcs/Main.c_v $
*/
/****************************************************************************
* Include files
****************************************************************************/
#include <Core/RtxCore.h>
#include <Ros/RosCfg.h>
#include <PortDef.h>
#include <Api/Api.h>
#include <Cola/Cola.h>
#include <Nvs/NvsDef.h>
#include <Protothreads/Protothreads.h>
#include <SwClock/SwClock.h>
#include <BuildInfo/BuildInfo.h>
#include <2lemetry/App2lemetry.h>
#include <PtApps/AppCommon.h>
#include <NetUtils/NetUtils.h>
#include <PtApps/AppShell.h>
#include <PtApps/AppLed.h>
#include <PtApps/AppWifi.h>
#include <PtApps/AppSntp.h>
#include <Drivers/DrvButtons.h>
#include <Drivers/DrvIntTemp.h>
#include <Drivers/DrvNtcTemp.h>
#include <Drivers/DrvI2cIntf.h>
#include <Drivers/DrvApds990x.h>
#include <Drivers/DrvHIH613x.h>
#include <Drivers/DrvLps331ap.h>
#include <Drivers/DrvLsm303dlhc.h> // accel
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/****************************************************************************
* Macro definitions
****************************************************************************/
// m2m.io broker dns address
#define BROKER_ADDRESS "q.m2m.io"
#define TCP_PORT 1883
// keep alive interval in seconds
#define MQTT_KEEP_ALIVE (30)
// If connection to MQTT server is lost (without request), it will automatically try to reconnect, every X seconds
// Use "0" value to disable automatic reconnect
#define MQTT_AUTO_RECONNECT (8)
// QoS used to publish data
#define MQTT_PUBLISH_QOS (0)
#define TMP_STR_LENGTH 150
#define CONNECT_FAILED_TIMEOUT (30*RS_T1SEC)
#define APP_NVS_OFFSET(x) (NVS_OFFSET(RtxAppData) + RSOFFSETOF(NvsDataType, x))
#define NVS_APP_NAME_LENGTH 8
#define NVS_USER_NAME_LENGTH 40
#define NVS_USER_DOMAIN_LENGTH 33
#define NVS_PWD_MD5_LENGTH 33
/****************************************************************************
* Enumerations/Type definitions/Structs
****************************************************************************/
typedef enum
{
WIM_WIFI_ON,
WIM_WIFI_OFF,
WIM_WIFI_SUSPEND,
WIM_WIFI_MAX,
} WifiIdleModeType;
typedef struct
{
rsuint8 AppName[NVS_APP_NAME_LENGTH]; // 2lemetry
rsuint16 SleepInterval;
rschar UserName[NVS_USER_NAME_LENGTH]; // 2lemetry account login (e-mail)
rschar UserDomain[NVS_USER_DOMAIN_LENGTH]; // 2lemetry domain associated with user
rschar pwdMD5[NVS_PWD_MD5_LENGTH]; // MD5 of user password
} NvsDataType;
typedef struct
{
struct pt ChildPt;
rsuint16 SleepInterval;
rsbool MultiSensor; // True, if MultiSensor board available
ApiSocketAddrType MqttServerAddr; // Address of MQTT server
rschar MacString[13]; // mac address
rschar UserName[NVS_USER_NAME_LENGTH]; // 2lemetry account login (e-mail)
rschar UserDomain[NVS_USER_DOMAIN_LENGTH];
rschar pwdMD5[NVS_PWD_MD5_LENGTH];
} AppDataType;
/****************************************************************************
* Global variables/const
****************************************************************************/
static const RosTimerConfigType DelayTimer = ROSTIMER(COLA_TASK, APP_DELAY_TIMEOUT, APP_DELAY_TIMER);
/****************************************************************************
* Local variables/const
****************************************************************************/
static RsListEntryType PtList;
char TmpStr[TMP_STR_LENGTH];
static AppDataType AppData;
static ApiMqttBrokerType *MqttBroker = NULL; // mqtt broker to use
// TODO: move
static char PubTopic[50];
/****************************************************************************
* Local Function prototypes
****************************************************************************/
void ApiMqttHandler(const RosMailType *Mail);
/****************************************************************************
* Implementation
***************************************************************************/
static void NvsInit(void)
{
RSASSERTSTATIC(RSOFFSETOF(NvsType, RtxAppData) == 0x0100); // ASSERT if AppData is moved in NvsType
NvsDataType nvsData;
// Read NVS data
NvsRead(NVS_OFFSET(RtxAppData), sizeof(NvsDataType), (rsuint8 *)&nvsData);
if (memcmp(nvsData.AppName, "2lemetry", NVS_APP_NAME_LENGTH))
{
// Init NVS params if AppName != "2lemetry"
memcpy(nvsData.AppName, "2lemetry", NVS_APP_NAME_LENGTH);
nvsData.SleepInterval = 1; // default update interval
nvsData.UserName[0] = '\0';
nvsData.UserDomain[0] = '\0';
nvsData.pwdMD5[0] = '\0';
NvsWrite(NVS_OFFSET(RtxAppData), sizeof(NvsDataType), (rsuint8 *)&nvsData);
}
// Update AppData
AppData.SleepInterval = nvsData.SleepInterval;
strcpy(AppData.UserName, nvsData.UserName);
strcpy(AppData.UserDomain, nvsData.UserDomain);
strcpy(AppData.pwdMD5, nvsData.pwdMD5);
}
static void BuildMacString(void)
{
ApiWifiMacAddrType * pMacAddr = (ApiWifiMacAddrType *)AppWifiGetMacAddr();
sprintf(AppData.MacString, "%02X%02X%02X%02X%02X%02X", (*pMacAddr)[0], (*pMacAddr)[1], (*pMacAddr)[2], (*pMacAddr)[3], (*pMacAddr)[4], (*pMacAddr)[5]);
}
/*****************************************************************************
* Sensor handling *
*****************************************************************************/
static void SensorInit(void)
{
// Init sensor drivers and create the sensors in App2lemetry
InitLsm303dlhc(); // accelerometer Init
DrvIntTemp_Init(); // Internal temperature sensor init
}
// Publish null-term string on MQTT topic; uses global MqttBroker
static void MqttPublish(char *Topic, char *MessageStr)
{
static rsuint16 MsgId; // Not used here, but useful to wait for publish result
if ((NULL != MqttBroker) && (MqttBroker->MqttConnected))
{
MsgId = MqttGenerateMessageId(MqttBroker);
SendApiMqttPublishReq(COLA_TASK, MqttBroker, Topic,
(const rsuint8 *)MessageStr, strlen(MessageStr), 0, MQTT_PUBLISH_QOS, MsgId);
//AppShellPrint(Topic);
AppShellPrint("Publish: ");
AppShellPrint(MessageStr);
AppShellPrint("\n");
}
}
static PT_THREAD(PtDoSensorUpdate(struct pt *Pt, const RosMailType *Mail))
{
static struct pt ChildPt;
static rsint16 tempX10;
static AccAxesRaw_t accelReading;
PT_BEGIN(Pt);
if (!AppData.UserDomain[0] || !PubTopic[0])
{
// Do not update sensors if no PubTopic, or no UserDomain (device not yet registered in cloud)
PT_EXIT(Pt);
}
// build JSON message for m2m.io platform
InitJsonMsg(TmpStr);
/*SL: Note: this sensors not yet accepted by dashboard, but could be added later
// 1. Read all sensors and update the sensor data stored in App2lemetry
if(AppData.MultiSensor)
{
static rsint16 data;
static rsint32 datal;
AppShellPrint("------ MultiSensor updates:\n");
// TODO:
//PT_SPAWN(Pt, &ChildPt, PtDrvIntTempMeasure(&ChildPt, Mail, &tempX10));
PT_SPAWN(Pt, &ChildPt, PtDrvNtcTempMeasure(&ChildPt, Mail, &data));
addNumberValToMsg("NtcTempX10", data, TmpStr);
PT_SPAWN(Pt, &ChildPt, PtDrvApds990xGetLux(&ChildPt, Mail, &data));
addNumberValToMsg("Light", data, TmpStr);
PT_SPAWN(Pt, &ChildPt, PtDrvHIH613xMeasure(&ChildPt, Mail, &data));
addNumberValToMsg("Humidity", data, TmpStr);
PT_SPAWN(Pt, &ChildPt, PtDrvLps331apMeasure(&ChildPt, Mail, &datal, &data));
addNumberValToMsg("Pressure", datal, TmpStr);
addNumberValToMsg("LpsTempX10", data, TmpStr);
}
else */
{
//AppShellPrint("------ Built-in sensor update:\n");
// Measure internal temperature inside the EFM32 chip.
PT_SPAWN(Pt, &ChildPt, PtDrvIntTempMeasure(&ChildPt, Mail, &tempX10));
if (GetAccAxesRaw(&accelReading) != MEMS_SUCCESS)
{
AppShellPrint("ACCEL err\n");
}
else
{
AddNumberValToMsg("x", accelReading.AXIS_X, TmpStr);
AddNumberValToMsg("y", accelReading.AXIS_Y, TmpStr);
AddNumberValToMsg("z", accelReading.AXIS_Z, TmpStr);
}
AddNumberValToMsg("t", tempX10, TmpStr);
}
// end JSON msg
FinishJsonMsg(TmpStr);
MqttPublish(PubTopic, TmpStr);
PT_END(Pt);
}
/*****************************************************************************
* AppShell *
*****************************************************************************/
#if APP_SHELL_ENABLED == 1
rsbool AppShellPrintWelcomeCallback(void)
{
AppShellPrint("\n\n2lemetry\n");
snprintf(TmpStr, TMP_STR_LENGTH, "Version: %X.%X.%X.%X\n", VersionHexFormat >> 8, (rsuint8)VersionHexFormat, VersionBranchHexFormat, BldInfo_BuildNo);
AppShellPrint(TmpStr);
snprintf(TmpStr, TMP_STR_LENGTH, "Build time: 20%02X-%02X-%02X %02X:%02X\n", LinkDate[0], LinkDate[1], LinkDate[2], LinkDate[3], LinkDate[4]);
AppShellPrint(TmpStr);
if (AppWifiIpConfigIsStaticIp())
{
inet_ntoa(AppWifiIpv4GetAddress(), TmpStr);
AppShellPrint("Static IP: ");
AppShellPrint(TmpStr);
AppShellPrint("\n");
}
else
{
AppShellPrint("DHCP enabled\n");
}
AppShellPrint("SSID: ");
AppShellPrint((char *)AppWifiGetSsid(0));
AppShellPrint("\n");
snprintf(TmpStr, TMP_STR_LENGTH, "Interval: %d\n", AppData.SleepInterval);
AppShellPrint(TmpStr);
return TRUE;
}
#if APP_SHELL_HELP_CMD_ENABLED == 1
void AppShellPrintHelpCallback(void)
{
AppShellPrint("setinterval <sleep interval in sec>\n");
AppShellPrint("setuser <2lemetry user e-mail> <pwd>\n");
}
#endif
// Project specific handling of shell commands
static PT_THREAD(PtOnCommand(struct pt *Pt, const RosMailType *Mail, rsuint16 Argc, char *Argv[1]))
{
static struct pt onCommandChildPt;
PT_BEGIN(Pt);
if (Argc)
{
if (0 == strcmp(Argv[0], "setinterval"))
{
char *p;
AppData.SleepInterval = strtoul(Argv[1], &p, 0);
NvsWrite(APP_NVS_OFFSET(SleepInterval), sizeof(AppData.SleepInterval), (rsuint8 *)&AppData.SleepInterval);
if (AppData.SleepInterval)
{
AppShellPrint("OK\n");
}
else
{
AppShellPrint("Failed!\n");
}
}
else if (0 == strcmp(Argv[0], "setuser"))
{
if (Argc == 3)
{
AppData.UserDomain[0] = '\0';
PT_SPAWN(Pt, &onCommandChildPt, PtApp2lemetrySetUser(&onCommandChildPt, Mail, Argv[1], Argv[2], "RTX", AppData.MacString));
App2lemetryGetUserInfo(AppData.UserName, AppData.UserDomain, AppData.pwdMD5);
if (AppData.UserDomain[0] == '\0')
{
// Activation failed
AppData.UserName[0] = '\0';
AppData.pwdMD5[0] = '\0';
AppShellPrint("Failed!\n");
}
else
{
AppShellPrint("OK - reboot!\n");
}
// store domain and pwdMD5 in NVS
NvsWrite(APP_NVS_OFFSET(UserName), NVS_USER_NAME_LENGTH, (rsuint8 *)AppData.UserName);
NvsWrite(APP_NVS_OFFSET(UserDomain), NVS_USER_DOMAIN_LENGTH, (rsuint8 *)AppData.UserDomain);
NvsWrite(APP_NVS_OFFSET(pwdMD5), NVS_PWD_MD5_LENGTH, (rsuint8 *)AppData.pwdMD5);
}
}
else
{
AppShellPrint("unknown cmd\n");
}
}
PT_END(Pt);
}
#endif
/*****************************************************************************
* Main application loop - PtMain *
*****************************************************************************/
static PT_THREAD(PtMain(struct pt *Pt, const RosMailType *Mail))
{
rschar clientIDWithMAC[20]; // Client ID for this board - use MAC address for this now
PT_BEGIN(Pt);
// Power on/reset the Wifi chip
PT_SPAWN(Pt, &AppData.ChildPt, PtAppPrologueNoConnect(&AppData.ChildPt, Mail));
// Set power save parameters
AppWifiSetPowerSaveProfile(POWER_SAVE_LOW_IDLE);
// build a unique client ID using the MAC address
BuildMacString();
#if APP_SHELL_ENABLED == 1
// Init AppShell - common shell handler
AppShellInit(&PtList, PtOnCommand);
PT_YIELD_UNTIL(Pt, FALSE == AppShellIsActive());
#endif
// Init MQTT - create broker and configure it
AppData.MqttServerAddr.Domain = ASD_AF_INET; // IPv4
AppData.MqttServerAddr.Port = TCP_PORT;
if (0 == strlen(AppData.UserDomain))
{
AppShellPrint("This device is not registered!\n");
AppShellPrint("Create account on http://rtx.m2m.io\nand register device with 'setuser <login> <pwd>' command.\n");
}
// build the topic on which to publish sensor data
// topic is <domain>/rtx/<MAC Address>
App2lemetryGeneratePubTopic(PubTopic, AppData.UserDomain, AppData.MacString);
// Main loop - should never end!
while (1)
{
// Don't do anything if the the AppShell is active
#if APP_SHELL_ENABLED == 1
while (AppShellIsActive())
{
PT_YIELD(Pt);
}
#endif
// Connect to the AP if not connected
while (!AppWifiIsConnected())
{
AppLedSetLedState(LED_STATE_CONNECTING);
AppShellPrint("Conn to Wi-Fi AP..\n");
PT_SPAWN(Pt, &AppData.ChildPt, PtAppWifiConnect(&AppData.ChildPt, Mail));
if (AppWifiIsConnected())
{
// Connected!
AppLedSetLedState(LED_STATE_CONNECTED);
AppShellPrint("WiFi connected!\n");
// Update DNS client with default gateway addr
SendApiDnsClientAddServerReq(COLA_TASK, AppWifiIpv4GetGateway(), AppWifiIpv6GetAddr()->Gateway);
PT_WAIT_UNTIL(Pt, IS_RECEIVED(API_DNS_CLIENT_ADD_SERVER_CFM));
// Perform DNS lookup of q.m2m.io, store in AppData
SendApiDnsClientResolveReq(COLA_TASK, false, strlen(BROKER_ADDRESS), (rsuint8*)BROKER_ADDRESS);
PT_WAIT_UNTIL(Pt, IS_RECEIVED(API_DNS_CLIENT_RESOLVE_CFM));
if (((ApiDnsClientResolveCfmType*)Mail)->Status == RSS_SUCCESS)
{
AppData.MqttServerAddr.Ip.V4.Addr = ((ApiDnsClientResolveCfmType*)Mail)->IpV4;
}
// TEMPORARY - temp fix to allow DNS client time to close the socket before it is reused
RosTimerStart(APP_DELAY_TIMER, 500*RS_T1MS, &DelayTimer);
PT_YIELD_UNTIL(Pt, IS_RECEIVED(APP_DELAY_TIMEOUT));
if (MqttBroker == NULL)
{
// 1) Create MQTT broker and set it's parameters
sprintf(clientIDWithMAC, "RTX-%s", AppData.MacString);
sprintf(TmpStr, "create broker user=%s, md5=%s, clientID=%s\n", AppData.UserName, AppData.pwdMD5, clientIDWithMAC);
AppShellPrint(TmpStr);
if (0 == strlen(AppData.UserDomain))
{
// connection without login/password, only for device registration
MqttBroker = MqttCreateBroker(NULL, AppData.MqttServerAddr, clientIDWithMAC, NULL, NULL, MQTT_AUTO_RECONNECT);
}
else
{
// connection with login/password for data posting
MqttBroker = MqttCreateBroker(NULL, AppData.MqttServerAddr, clientIDWithMAC, AppData.UserName, AppData.pwdMD5, MQTT_AUTO_RECONNECT);
}
MqttSetKeepAlive(MqttBroker, MQTT_KEEP_ALIVE);
// use also MqttSetExtendedParams() if needed
App2lemetrySetMqttBroker(MqttBroker);
}
if ((NULL != MqttBroker) && (FALSE == MqttBroker->MqttConnected))
{
// Connect to MQTT server
SendApiMqttConnectReq(COLA_TASK, MqttBroker);
PT_WAIT_UNTIL(Pt, IS_RECEIVED(API_MQTT_CONNECT_CFM));
if (((ApiMqttConnectCfmType *)Mail)->Status != RSS_SUCCESS)
{
sprintf(TmpStr, "Connect to MQTT server failed: %d\n", ((ApiMqttConnectCfmType *)Mail)->Status);
AppShellPrint(TmpStr);
}
else
{
AppShellPrint("Connected to MQTT!\n");
}
// MQTT connect finished
}
}
else
{
// Not connected to AP!
AppLedSetLedState(LED_STATE_ERROR_NO_AP);
AppShellPrint("WiFi NOT connected\n");
// Power off wifi
PT_SPAWN(Pt, &AppData.ChildPt, PtAppWifiPowerOff(&AppData.ChildPt, Mail));
// Start timer and try again when it expires or button is pressed
RosTimerStart(APP_DELAY_TIMER, CONNECT_FAILED_TIMEOUT, &DelayTimer);
PT_WAIT_UNTIL(Pt, IS_RECEIVED(APP_DELAY_TIMEOUT) ||
IS_RECEIVED(KEY_MESSAGE));
if (IS_RECEIVED(KEY_MESSAGE) && Mail->P1.P1 == KEY_WPS)
{
// Do WPS!
AppLedSetLedState(LED_STATE_WPS_ONGOING);
PT_SPAWN(Pt, &AppData.ChildPt, PtAppWifiDoWps(&AppData.ChildPt, Mail));
AppLedSetLedState(LED_STATE_IDLE);
}
else
{
// Power on wifi again
PT_SPAWN(Pt, &AppData.ChildPt, PtAppWifiPowerOn(&AppData.ChildPt, Mail));
}
}
}
// Connected to AP!
while (AppWifiIsConnected())
{
// Do sensor reading and update the server
PT_SPAWN(Pt, &AppData.ChildPt, PtDoSensorUpdate(&AppData.ChildPt, Mail));
// Wait here to it is time to run again
RosTimerStart(APP_DELAY_TIMER, AppData.SleepInterval*RS_T1SEC, &DelayTimer);
PT_YIELD_UNTIL(Pt, IS_RECEIVED(APP_DELAY_TIMEOUT));
}
}
PT_END(Pt);
}
/*****************************************************************************
* CoLA TASK *
*****************************************************************************/
static void Init(void)
{
// Read/init NVS data
NvsInit();
// Init the Protothreads lib
PtInit(&PtList);
// Init the Buttons driver
DrvButtonsInit();
// Init the LED application
AppLedInit(&PtList);
// Init the WiFi management application
AppWifiInit(&PtList);
//Init the sensors
SensorInit();
// Start the Main protothread
PtStart(&PtList, PtMain, NULL, NULL);
}
void ColaTask(const RosMailType *Mail)
{
// Pre-dispatch mail handling
switch (Mail->Primitive)
{
case INITTASK:
Init();
break;
case TERMINATETASK:
RosTaskTerminated(ColaIf->ColaTaskId);
break;
case API_GPIO_INTERRUPT_IND:
// Dispatch API_GPIO_INTERRUPT_IND to the button driver and return
DrvButtonsOnMail(Mail);
return;
}
// Plugin of Mqtt task - Mails to the Mqtt task is handled here as we can
// have one CoLA task only.
ApiMqttHandler(Mail);
// Dispatch mail to all protothreads started
PtDispatchMail(&PtList, Mail); // If Mail is Incoming TCP packet, PtDispatch could handle it or not
}
// End of file.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment