Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
|
The target system to be monitored consists of two banks of solar panels connected to two Outback MX60 controllers. The controllers attempt to maintain charge on two banks of batteries. When inadequate conditions exist due to weather, the battery charge is not maintained at a level suitable to provide power to the facility. When this happens, a generator is used to provide charge to the batteries. The two MX60 units are connected via a power over Ethernet switch to an Outback Mate display unit. The Outback Mate provides an isolated RS-232 output. The Azure Sphere is interfaced to this output and provides feedback to the facility operator for solar panel voltage and current and battery voltage and charger current. The system also monitors facility demand current on 110 VAC. This 110 VAC is generated by inverters connected to the batteries. All of these components are the basis of a smart grid for a facility.
Complementary to the monitoring system is a generator controller, which is not part of this project. The generator controller starts and stops the generator as needed and provides feedback on generator health, including fuel level.
The overall goal of this combination is to minimize the time that the generator must be run to conserve fuel. Analytics may be used to reason based upon historical facility demand, weather conditions and other factors to optimize for minimum fuel consumption while preserving battery health.
MX60 C MX60 Parsing Code
C/C++#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/neutrino.h>
#include <sys/time.h>
#include <hw/inout.h>
#include <signal.h>
#include <curl/curl.h>
#include "mx60.h"
#define HS_ADC_SHMEM_NAME "/mx60"
#define PROGNAME "mx60-recorder"
mx60_t* mx60_buf;
static int keep_running = 1;
FILE* out;
void create_shmem()
{
int shmem_fd;
int amount = 3 * sizeof(mx60_t);
shmem_fd = shm_open( HS_ADC_SHMEM_NAME, O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
if ( shmem_fd == -1 ) {
fprintf(stderr, "%s: error creating the shared memory object '%s': %s\n",
PROGNAME, HS_ADC_SHMEM_NAME, strerror(errno) );
exit( EXIT_FAILURE );
}
ftruncate( shmem_fd, amount );
mx60_buf = (mx60_t*) mmap( 0, amount, PROT_READ|PROT_WRITE|PROT_NOCACHE, MAP_SHARED, shmem_fd, 0 );
close( shmem_fd );
}
/* to break us out of character handler loop */
void killHandler(int signo) {
if (signo == SIGINT || signo == SIGTERM) {
keep_running = 0;
}
}
const char* default_in_filename = "/dev/ser2";
const char* default_out_filename = "test.csv";
int main(int argc, char *argv[]) {
int out_fd;
int in_fd;
char* out_filename = (char*) default_out_filename;
char* in_filename = (char*) default_in_filename;
int n;
int flags;
int position;
int channel;
char buf[128];
char* endptr;
char opt;
struct timeval tv;
while ((opt = getopt(argc, argv, "i:o:")) != -1) {
switch(opt) {
case 'i': // input filename (port)
in_filename = optarg;
break;
case 'o': // output filename
out_filename = optarg;
break;
}
}
in_fd = open(in_filename, O_RDONLY, NULL);
if (in_fd <= 0) {
printf("could not open %s\n", in_filename);
return EXIT_FAILURE;
}
out_fd = open(out_filename, O_CREAT | O_TRUNC | O_WRONLY | O_SYNC ,
S_IRUSR | S_IRGRP | S_IROTH);
if (out_fd <= 0) {
printf("could not open %s\n", out_filename);
return 1;
}
out = fopen("/dev/null", "w");
// flush the port
flags = fcntl(in_fd, F_GETFL, 0);
fcntl(in_fd, F_SETFL, flags | O_NONBLOCK);
do {
n = read(in_fd, &opt, 1);
} while (n != -1);
fcntl(in_fd, F_SETFL, flags & ~(O_NONBLOCK));
signal(SIGINT, killHandler);
signal(SIGTERM, killHandler);
create_shmem();
while (keep_running) {
do {
read(in_fd, &opt, 1);
} while (opt != 10);
read(in_fd, &opt, 1);
if (opt == 'A') {
channel = 0;
} else if (opt == 'B') {
channel = 1;
} else if (opt == 'C') {
channel = 2;
} else {
printf("unexpected channel %d\n", opt);
continue;
}
gettimeofday(&tv, NULL);
sprintf(buf, "%d.%06d,%c,", tv.tv_sec, tv.tv_usec, opt);
write(out_fd, buf, strlen(buf));
read(in_fd, &opt, 1); // skip comma
position = 0;
do {
read(in_fd, &buf[position], 1);
} while (buf[position++] != 10);
buf[position-1] = '\n';
buf[position] = 0;
printf("%d %d.%06d %s", channel, tv.tv_sec, tv.tv_usec, buf);
write(out_fd, buf, strlen(buf));
strtol(buf, &endptr, 0); // skip unused field
endptr++; // skip comma
if (endptr[0] == '0') endptr++; // skip leading zero
mx60_buf[channel].charger_current = strtol(endptr, &endptr, 10);
endptr++; // skip comma
if (endptr[0] == '0') endptr++; // skip leading zero
mx60_buf[channel].pv_current = strtol(endptr, &endptr, 10);
endptr++; // skip comma
if (endptr[0] == '0') endptr++; // skip leading zero
if (endptr[0] == '0') endptr++; // skip leading zero
mx60_buf[channel].pv_voltage = strtol(endptr, &endptr, 10);
endptr++; // skip comma
if (endptr[0] == '0') endptr++; // skip leading zero
if (endptr[0] == '0') endptr++; // skip leading zero
mx60_buf[channel].daily_kwh = strtol(endptr, &endptr, 10) / 10.0;
endptr += 4; // skip comma, 0, comma
strtol(endptr, &endptr, 0); // skip aux mode
endptr++;
if (endptr[0] == '0') endptr++; // skip leading zero
if (endptr[0] == '0') endptr++; // skip leading zero
mx60_buf[channel].error_mode = strtol(endptr, &endptr, 10);
endptr++; // skip comma
if (endptr[0] == '0') endptr++; // skip leading zero
mx60_buf[channel].charger_mode = strtol(endptr, &endptr, 10);
endptr++; // skip comma
if (endptr[0] == '0') endptr++; // skip leading zero
if (endptr[0] == '0') endptr++; // skip leading zero
mx60_buf[channel].battery_voltage = strtol(endptr, &endptr, 10) / 10.0;
mx60_buf[channel].pv_power = mx60_buf[channel].pv_current * mx60_buf[channel].pv_voltage;
}
close(in_fd);
close(out_fd);
return EXIT_SUCCESS;
}
MX60 header file
C Header File/*
* mx60.h
*
* Author: tpeterson
*/
#ifndef MX60_H_
#define MX60_H_
typedef struct mx60_s {
float timestamp;
int charger_current;
int pv_current;
int pv_voltage;
float daily_kwh;
int error_mode;
int charger_mode;
float battery_voltage;
int pv_power;
} mx60_t;
#endif /* MX60_H_ */
Visual Studio C code
C/C++/* Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License. */
// This sample C application for Azure Sphere demonstrates how to use a UART (serial port),
// an ADC and the Wifi interface.
//
// It uses the API for the following Azure Sphere application libraries:
// - UART (serial port)
// - ADC (analog to digital converter)
// - Wifi
// - log (messages shown in Visual Studio's Device Output window during debugging)
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <stdio.h>
// applibs_versions.h defines the API struct versions to use for applibs APIs.
#include "applibs_versions.h"
#include "epoll_timerfd_utilities.h"
#include <applibs/uart.h>
#include <applibs/log.h>
#include <applibs/adc.h>
#include <applibs/networking.h>
// By default, this sample is targeted at the MT3620 Reference Development Board (RDB).
// This can be changed using the project property "Target Hardware Definition Directory".
// This #include imports the sample_hardware abstraction from that hardware definition.
#include <hw/sample_hardware.h>
// File descriptors - initialized to invalid value
static int uartFd = -1;
static int epollFd = -1;
static int pollTimerFd = -1;
static int adcControllerFd = -1;
// The size of a sample in bits
static int sampleBitCount = -1;
// The maximum voltage
static float sampleMaxVoltage = 2.5f;
// Termination state
static volatile sig_atomic_t terminationRequired = false;
/// <summary>
/// Signal handler for termination requests. This handler must be async-signal-safe.
/// </summary>
static void TerminationHandler(int signalNumber)
{
// Don't use Log_Debug here, as it is not guaranteed to be async-signal-safe.
terminationRequired = true;
}
// send data to a server over a UDP socket
static int sendData(char* content) {
const char* hostname = "xxx.xxx.xxx.xxx"; // replace with your server IP address
const char* portname = "9090";
struct addrinfo hints;
bool isNetworkingReady = false;
if ((Networking_IsNetworkingReady(&isNetworkingReady) < 0) || !isNetworkingReady) {
Log_Debug("\nNot doing download because there is no internet connectivity.\n");
return -1;
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = 0;
hints.ai_flags = AI_ADDRCONFIG;
struct addrinfo* res = 0;
int err = getaddrinfo(hostname, portname, &hints, &res);
if (err != 0) {
Log_Debug("failed to resolve remote socket address (err=%d)", err);
return err;
}
int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (fd == -1) {
Log_Debug("%s", strerror(errno));
goto sendErr;
}
if (sendto(fd, content, sizeof(content), 0,
res->ai_addr, res->ai_addrlen) == -1) {
Log_Debug("%s", strerror(errno));
}
sendErr:
freeaddrinfo(res);
return 0;
}
/// <summary>
/// Handle polling timer event: takes a single reading from ADC channelId,
/// every second, outputting the result.
/// </summary>
static void AdcPollingEventHandler(EventData *eventData)
{
if (ConsumeTimerFdEvent(pollTimerFd) != 0) {
terminationRequired = true;
return;
}
static char buffer[64];
uint32_t value;
int result = ADC_Poll(adcControllerFd, SAMPLE_POTENTIOMETER_ADC_CHANNEL, &value);
if (result < -1) {
Log_Debug("ADC_Poll failed with error: %s (%d)\n", strerror(errno), errno);
terminationRequired = true;
return;
}
float voltage = ((float)value * sampleMaxVoltage) / (float)((1 << sampleBitCount) - 1);
sprintf(buffer, "%6.4f", value);
sendData(buffer);
Log_Debug("The out sample value is %.3f V\n", voltage);
}
// event handler data structures. Only the event handler field needs to be populated.
static EventData adcPollingEventData = { .eventHandler = &AdcPollingEventHandler };
/// <summary>
/// Handle UART event: if there is incoming data, process it.
/// </summary>
static void UartEventHandler(EventData *eventData)
{
const size_t receiveBufferSize = 256;
uint8_t receiveBuffer[receiveBufferSize + 1]; // allow extra byte for string termination
ssize_t bytesRead;
// Read incoming UART data. It is expected behavior that messages may be received in multiple
// partial chunks.
bytesRead = read(uartFd, receiveBuffer, receiveBufferSize);
if (bytesRead < 0) {
Log_Debug("ERROR: Could not read UART: %s (%d).\n", strerror(errno), errno);
terminationRequired = true;
return;
}
if (bytesRead > 0) {
// Null terminate the buffer to make it a valid string, and print it
receiveBuffer[bytesRead] = 0;
sendData(receiveBuffer);
Log_Debug("UART received %d bytes: '%s'.\n", bytesRead, (char *)receiveBuffer);
}
}
static EventData uartEventData = {.eventHandler = &UartEventHandler};
/// <summary>
/// Set up SIGTERM termination handler, initialize peripherals, and set up event handlers.
/// </summary>
/// <returns>0 on success, or -1 on failure</returns>
static int InitPeripheralsAndHandlers(void)
{
struct sigaction action;
memset(&action, 0, sizeof(struct sigaction));
action.sa_handler = TerminationHandler;
sigaction(SIGTERM, &action, NULL);
epollFd = CreateEpollFd();
if (epollFd < 0) {
return -1;
}
// Create a UART_Config object, open the UART and set up UART event handler
UART_Config uartConfig;
UART_InitConfig(&uartConfig);
uartConfig.baudRate = 115200;
uartConfig.flowControl = UART_FlowControl_None;
uartFd = UART_Open(SAMPLE_UART, &uartConfig);
if (uartFd < 0) {
Log_Debug("ERROR: Could not open UART: %s (%d).\n", strerror(errno), errno);
return -1;
}
if (RegisterEventHandlerToEpoll(epollFd, uartFd, &uartEventData, EPOLLIN) != 0) {
return -1;
}
adcControllerFd = ADC_Open(SAMPLE_POTENTIOMETER_ADC_CONTROLLER);
if (adcControllerFd < 0) {
Log_Debug("ADC_Open failed with error: %s (%d)\n", strerror(errno), errno);
return -1;
}
sampleBitCount = ADC_GetSampleBitCount(adcControllerFd, SAMPLE_POTENTIOMETER_ADC_CHANNEL);
if (sampleBitCount == -1) {
Log_Debug("ADC_GetSampleBitCount failed with error : %s (%d)\n", strerror(errno), errno);
return -1;
}
if (sampleBitCount == 0) {
Log_Debug("ADC_GetSampleBitCount returned sample size of 0 bits.\n");
return -1;
}
int result = ADC_SetReferenceVoltage(adcControllerFd, SAMPLE_POTENTIOMETER_ADC_CHANNEL,
sampleMaxVoltage);
if (result < 0) {
Log_Debug("ADC_SetReferenceVoltage failed with error : %s (%d)\n", strerror(errno), errno);
return -1;
}
struct timespec adcCheckPeriod = { .tv_sec = 1,.tv_nsec = 0 };
pollTimerFd =
CreateTimerFdAndAddToEpoll(epollFd, &adcCheckPeriod, &adcPollingEventData, EPOLLIN);
if (pollTimerFd < 0) {
return -1;
}
return 0;
}
/// <summary>
/// Close peripherals and handlers.
/// </summary>
static void ClosePeripheralsAndHandlers(void)
{
Log_Debug("Closing file descriptors.\n");
CloseFdAndPrintError(uartFd, "Uart");
CloseFdAndPrintError(epollFd, "Epoll");
CloseFdAndPrintError(pollTimerFd, "Timer");
CloseFdAndPrintError(adcControllerFd, "ADC");
}
/// <summary>
/// Main entry point for this application.
/// </summary>
int main(int argc, char *argv[])
{
Log_Debug("MX60 application starting.\n");
if (InitPeripheralsAndHandlers() != 0) {
terminationRequired = true;
}
// Use epoll to wait for events and trigger handlers, until an error or SIGTERM happens
while (!terminationRequired) {
if (WaitForEventAndCallHandler(epollFd) != 0) {
terminationRequired = true;
}
}
ClosePeripheralsAndHandlers();
Log_Debug("Application exiting.\n");
return 0;
}
{
"SchemaVersion": 1,
"Name" : "UART_HighLevelApp",
"ComponentId" : "4f2d9823-dbbd-4740-a7dd-198c32ba34fe",
"EntryPoint": "/bin/app",
"CmdArgs": [],
"Capabilities": {
"Uart": [ "$SAMPLE_UART" ],
"Adc": [
"$SAMPLE_POTENTIOMETER_ADC_CONTROLLER"
],
"AllowedConnections": [ "my-server.com" ]
},
"ApplicationType": "Default"
}
Visual Studio Project File
Properties<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM">
<Configuration>Debug</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM">
<Configuration>Release</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{1a0db47c-ab49-4b10-985f-3feccc6526f3}</ProjectGuid>
<Keyword>AzureSphere</Keyword>
<RootNamespace>UART</RootNamespace>
<MinimumVisualStudioVersion>15.0</MinimumVisualStudioVersion>
<ApplicationType>Linux</ApplicationType>
<ApplicationTypeRevision>1.0</ApplicationTypeRevision>
<TargetLinuxPlatform>Generic</TargetLinuxPlatform>
<LinuxProjectType>{D51BCBC9-82E9-4017-911E-C93873C4EA2B}</LinuxProjectType>
<DebugMachineType>Device</DebugMachineType>
<PlatformToolset>GCC_AzureSphere_1_0</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
<TargetSysroot>3+Beta1909</TargetSysroot>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<TargetSysroot>2</TargetSysroot>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="Shared" />
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<TargetHardwareDirectory>..\..\..\Hardware\mt3620_rdb</TargetHardwareDirectory>
<TargetHardwareDefinition>sample_hardware.json</TargetHardwareDefinition>
</PropertyGroup>
<ItemGroup>
<ClCompile Include="main.c" />
<ClCompile Include="epoll_timerfd_utilities.c" />
<ClInclude Include="epoll_timerfd_utilities.h" />
<UpToDateCheckInput Include="app_manifest.json" />
<ClInclude Include="applibs_versions.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
<ItemDefinitionGroup>
<Link>
<LibraryDependencies>applibs;pthread;gcc_s;c</LibraryDependencies>
<AdditionalOptions>-Wl,--no-undefined -nodefaultlibs %(AdditionalOptions)</AdditionalOptions>
</Link>
<ClCompile>
<AdditionalOptions>-Werror=implicit-function-declaration %(AdditionalOptions)</AdditionalOptions>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">D:\development\azure-sphere\azure-sphere-samples\Hardware\mt3620_rdb\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
</Project>
Comments