I'm trying to find out how to implement a singleton class. Here's the background...... I've been playing around with reading and writing to files on an SD card. I've written a very basic 'logger' class to write error messages and other stuff to a log file. So far so good, I can include the class in another class or a sketch and I can log messages to the file without problems.
I now want to include the 'logger' object in all of the classes in my arduino application, but I want all of my classes to write to the same log file on the SD card. My research so far tells me I need to implement this as a 'singleton class' so there will only ever be one copy of my 'logger' object.
At the moment my header file looks like this.....
#ifndef Logger_h
#define Logger_h
extern "C" {
#include <string.h>
}
#include <stdlib.h>
#include "WProgram.h"
#include "Logger.h" // this file
#include <MikeSDFat.h>
#include <Sd2Card.h>
#include <FatStructs.h>
#include <Sd2PinMap.h>
#include <SdFat.h>
#include <SdFatmainpage.h>
#include <SdFatUtil.h>
#include <SdInfo.h>
class Logger
{
public:
Logger(); // Constructor
void writeLine(char * line); // Write a line of data to the log file
void close(); // Close the file
private:
// Member variables
MySDFat logFile; // needed to allow access to the logger file.
int state; // used to determin the state of the file (open, closed etc)
int lineCount;
};
As an Arduino (normally) has only one thread, does your class really need to be a singleton? It is no multitasking environment where different objects run in different threads. so ...
wrt the class,
add the filename to the constructor That enables you to have different loggings (different sketches) on the same SDcard.
add a flag to writeline indicating type of log; use an enum LEVEL { DEBUG, INFO, WARNING, ERROR, MAYDAY }
add a timestamp before every string (prefered from a RTC if available)
Does this mean an Arduino can be multithreaded? if so how? I thought arduino was single threaded only?
With respect to the original question, firstly, when a class is intrinsically linked to a single physical device it makes sense to have only a single instance of that class. Secondly, I have created multiple instances of my logger object, and although it does compile and run without error, only the first instance created actually manages to write to the file. This code snippet illustrates the problem?.
This runs without error but the resulting log file contains the lines written by the first instance of the object, not the second i.e......
Line one
Line two
Line three
Line four
If I reverse the order of the declarations then the file gets the other set of lines instead.
Does this mean an Arduino can be multithreaded? if so how? I thought arduino was single threaded only?
No, an Arduino can not be multi-threaded. That requires an operating system to manage which thread is running. The Arduino does not have an operating system.
Basically I have 2 questions....
is it possible to implement a singleton class?
if the answer is 'yes' how do I do it?
Typically, a singleton class has a private constructor and a public method to get the existing (static) instance. Your class has a public constructor, no static instance, and no method that returns that static instance.
#include <SdFat.h>
#include <SdFatmainpage.h>
#include <SdFatUtil.h>
#include <SdInfo.h>
class Logger
{
public:
Logger() {} ; // Constructor does nothing now as no per logger state
void writeLine(char * line);
void close() ;
private:
void open() ;
// Static Member variables that implement the singleton
static MySDFat logFile;
static char state;
static int lineCount;
};
and the library code is
#include <Logger.h>
// initialise the static memeber variables.
MySDFat Logger::logFile ; // set constructor parameters here as/and if required.
#define CLOSED 0
#define OPENED 1
char Logger::state = CLOSED ;
int Logger::lineCount = 0 ;
// Ensure the single chosen file is open for logging
void Logger::open()
{
if (state != OPENED)
{
// open the file :)
state = OPENED ;
}
}
void Logger::close()
{
if (state == OPENED)
{
// close the file :)
state = CLOSED ;
}
}
void Logger::writeLine(char *line)
{
open() ; // open file
// logFile.writeLine(line) ;
lineCount ++ ;
}
This pattern (adjusted for the way the code really works) should make your test example (above) log all of the lines from both Logger objects.
It will not work if you use logging in the interrupt handlers as well as in the main loop of the code.
And doing that is a MUCH bigger problem, and also not sensible IMHO.