Last active
July 23, 2018 18:34
-
-
Save m2mIO-gister/5155641 to your computer and use it in GitHub Desktop.
2lemetry RTX Application
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/**************************************************************************** | |
* 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