Page MenuHomePhabricator

No OneTemporary

diff --git a/Readme.txt b/Readme.txt
new file mode 100644
--- /dev/null
+++ b/Readme.txt
@@ -0,0 +1,52 @@
+This is a simple daemon for the D-Link DNS-320L NAS system.
+
+ It was developed on knowledge gained from reverse-engineering and spying
+ on the original D-Link system software.
+ Parts of this work are based on fan-daemon.py by Lorenzo Martignoni.
+
+ (c) 2013 Andreas Boehler, andreas _AT_ aboehler.at
+
+
+How it works
+============
+
+ On bootup, dns320l-daemon is executed and runs as a deamon. On startup, the
+ daemon can optionally read the RTC and set the system time to the RTC time.
+ It also sends the DeviceReady command to the MCU so that the Power LED stops
+ blinking.
+ Afterwards, it goes to fan control loop.
+
+ If the daemon is killed (e.g. during system shutdown), it sends the
+ DeviceShutdown command with a timeout of 10 seconds to the MCU. After 10
+ seconds, the MCU kills power.
+
+ For normal operation a socket server on port 57367 is provided. You can
+ connect with any telnet client to this port and work with the device.
+ Just type "help" for a list of available commands.
+
+Installation
+============
+
+ Either compile the software on your NAS by typing "make" or cross-compile
+ it from your host computer.
+ Put the binary to /usr/bin and the config file to /etc. Adapt the parameters
+ to your needs, leave to defaults if unsure.
+ A systemd unit is provided which can be copied to /etc/systemd/system and
+ enabled by running "systemctl enable dns320l-daemon"
+
+Disclaimer
+==========
+
+ This program is free software: you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option) any later
+ version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see <http://www.gnu.org/licenses/>.
+
diff --git a/dns320l-daemon.c b/dns320l-daemon.c
--- a/dns320l-daemon.c
+++ b/dns320l-daemon.c
@@ -1,671 +1,1054 @@
+/*
+
+ Simple system daemon for D-Link DNS-320L
+
+ (c) 2013 Andreas Boehler, andreas _AT_ aboehler.at
+
+ This code is based on a few other people's work and in parts shamelessly copied.
+ The ThermalTable was provided by Lorenzo Martignoni and the fan control
+ algorithm is based on his fan-daemon.py implementation.
+
+ The MCU protocol was reverse engineered by strace() calls to up_send_daemon and
+ up_read_daemon of the original firmware.
+
+
+
+ This program is free software: you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option) any later
+ version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
#include <errno.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <signal.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/types.h>
+#include <sys/time.h>
+#include <time.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/uio.h>
#include <syslog.h>
#include <iniparser.h>
#include "dns320l.h"
int ls;
int fd;
+/** @file dns320l-daemon.c
+ @brief Implementation of a free system daemon replacement for
+ the D-Link DNS-320L NAS
+ @author Andreas Boehler, andreas _AT_ aboehler.at
+ @version 1.0
+ @date 2013/09/12
+*/
+
+/** <i>Function</i> that reads a GPIO value from sysfs interface.
+ @param gpio The GPIO number to read
+ @param value Pointer where the value is to be put
+ @param gpioDir Pointer containing the sysfs path to the GPIO subdir
+ @return The GPIO's value
+ */
int gpio_get_value(unsigned int gpio, unsigned int *value, char *gpioDir)
{
int fd, len;
char buf[100];
char ch;
len = snprintf(buf, sizeof(buf), "%s/gpio%d/value", gpioDir, gpio);
fd = open(buf, O_RDONLY);
if (fd < 0) {
syslog(LOG_ERR, "gpio/get-value");
return fd;
}
read(fd, &ch, 1);
if (ch != '0') {
*value = 1;
} else {
*value = 0;
}
close(fd);
return 0;
}
-
+/** <i>Function</i> that cleans up a socket after shutdown
+ @param shut If 1, the socket is shutdown first
+ @param s The socket to work on
+ @param howmany Number of sockets to close?
+*/
void cleanup(int shut,int s,int howmany)
{
int retval;
/*
* Shutdown and close sock1 completely.
*/
if (shut)
{
retval = shutdown(s,howmany);
if (retval == -1)
syslog(LOG_ERR, "shutdown");
}
retval = close (s);
if (retval)
syslog(LOG_ERR, "close");
}
+/** <i>Function</i> that is called by the OS upon sending a signal
+ to the application
+ @param sig The signal number received
+*/
static void sighandler(int sig)
{
syslog(LOG_DEBUG, "Signal Handler called\n");
switch(sig)
{
case SIGINT:
cleanup(0, ls, 1);
exit(EXIT_SUCCESS);
break;
case SIGTERM:
cleanup(0, ls, 1);
syslog(LOG_INFO, "Shutting down machine in 10s...\n");
SendCommand(fd, DeviceShutdownCmd, 0);
exit(EXIT_SUCCESS);
break;
}
}
+/** <i>Function</i> that sets interface attributes on a given
+ serial port.
+ @param fd The file descriptor (serial port) to work with
+ @param speed The speed the interface to configure
+ @param parity Use parity or not
+ @return 0 on success, otherwise 1
+*/
int set_interface_attribs (int fd, int speed, int parity)
{
struct termios tty;
memset (&tty, 0, sizeof tty);
if (tcgetattr (fd, &tty) != 0)
{
syslog(LOG_ERR, "error %d from tcgetattr", errno);
return -1;
}
cfsetospeed (&tty, speed);
cfsetispeed (&tty, speed);
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8-bit chars
// disable IGNBRK for mismatched speed tests; otherwise receive break
// as \000 chars
tty.c_iflag &= ~IGNBRK; // ignore break signal
tty.c_lflag = 0; // no signaling chars, no echo,
// no canonical processing
tty.c_oflag = 0; // no remapping, no delays
tty.c_cc[VMIN] = 0; // read doesn't block
tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl
tty.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
// enable reading
tty.c_cflag &= ~(PARENB | PARODD); // shut off parity
tty.c_cflag |= parity;
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CRTSCTS;
if (tcsetattr (fd, TCSANOW, &tty) != 0)
{
syslog(LOG_ERR, "error %d from tcsetattr", errno);
return -1;
}
return 0;
}
+/** <i>Function</i> that sets an interface to either blocking
+ or non-blocking mode
+ @param fd The file descriptor to work with
+ @param should_block Flag whether it should block or not
+*/
void set_blocking (int fd, int should_block)
{
struct termios tty;
memset (&tty, 0, sizeof tty);
if (tcgetattr (fd, &tty) != 0)
{
syslog(LOG_ERR, "error %d from tggetattr", errno);
return;
}
tty.c_cc[VMIN] = should_block ? 1 : 0;
tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout
if (tcsetattr (fd, TCSANOW, &tty) != 0)
syslog(LOG_ERR, "error %d setting term attributes", errno);
}
-int CheckResponse(char *buf, const char *cmd, int len)
+/** <i>Function</i> that checks the first few bytes of the MCU's response
+ whether it corresponds to the sent command
+ @param buf The buffer to compare
+ @param cmd The command that was sent
+ @param len The lenght of the command
+ @return SUCCESS on success, otherwise ERR_WRONG_ANSWER
+*/
+int CheckResponse(char *buf, char *cmd, int len)
{
int i;
int tmp;
+ // Attention, 5 is hardcoded here and never checked!
for(i=0;i<5;i++)
{
if(buf[i] != cmd[i])
{
syslog(LOG_ERR, "Char %i is %i but should be %i\n", i, buf[i], cmd[i]);
return ERR_WRONG_ANSWER;
}
}
if(buf[len-1] != cmd[len-1])
return ERR_WRONG_ANSWER;
- tmp = (unsigned char)buf[5];
- return tmp;
+ return SUCCESS;
}
-int SendCommand(int fd, const char *cmd, int checkAnswer)
+/** <i>Function</i> that sends a command to the MCU and waits
+ for response and/or ACK.
+ @param fd The serial port to work on
+ @param cmd The command to send
+ @param outArray An array where the response shall be put, can be NULL for no response
+ @return SUCCESS, ERR_WRONG_ANSWER or the number of bytes received
+ */
+int SendCommand(int fd, char *cmd, char *outArray)
{
int n;
int i;
- unsigned int tmp;
+ int j;
char buf[15]; // We need to keep the DateAndTime values here
// Yes, we're sending byte by byte here - b/c the lenght of
// commands and responses can vary!
i=0;
do
{
write(fd, &cmd[i], 1);
i++;
} while(cmd[i-1] != CMD_STOP_MAGIC);
i=0;
do
{
n = read(fd, &buf[i], 1);
i++;
} while((n == 1) && (buf[i-1] != CMD_STOP_MAGIC));
if(buf[i-1] != CMD_STOP_MAGIC)
{
syslog(LOG_ERR, "Got no stop magic!\n");
return ERR_WRONG_ANSWER;
}
else
{
- if(checkAnswer)
+ // If outArray is not NULL, an answer was requested
+ if(outArray != NULL)
{
- tmp = CheckResponse(buf, cmd, i);
+ CheckResponse(buf, cmd, i);
+ // Copy the answer to the outArray
+ for(j=0; j<i; j++)
+ {
+ outArray[j] = buf[j];
+ }
usleep(20000); // Give the µC some time to answer...
+ // Wait for ACK from Serial
i=0;
do
{
n = read(fd, &buf[i], 1);
i++;
} while((n == 1) && (buf[i-1] != CMD_STOP_MAGIC));
if(buf[i-1] != CMD_STOP_MAGIC)
{
syslog(LOG_ERR, "Got no stop magic!\n");
return ERR_WRONG_ANSWER;
}
CheckResponse(buf, AckFromSerial, i);
- return tmp;
+ syslog(LOG_DEBUG, "Returning %i read bytes\n", n);
+ return j;
}
+ // Only wait for ACK if no response is expected
else
{
return CheckResponse(buf, AckFromSerial, i);
}
}
}
+/** <i>Function</i> that handles commands received by the socket
+ and puts the response back into the retMessage buffer
+ @param message The message that was received (the command)
+ @param messageLen The lenght of the received message
+ @param retMessage Pointer to an output array for the response message
+ @parma bufSize The size of the message buffer
+ @return 0 on success, 1 on failure, 2 for quit and 3 for daemon shutdown
+*/
int HandleCommand(char *message, int messageLen, char *retMessage, int bufSize)
{
int tmp;
int len;
int i;
- char cmp[] = "DeviceReady";
+ time_t rtcTime;
+ time_t sysTime;
+ struct timeval setTime;
+ char timeStr[100];
+ struct tm strTime;
+ struct tm *strSetTime;
+ char buf[15];
+ char cmdBuf[15];
+
syslog(LOG_DEBUG, "Handling Command: %s\n", message);
+ // This is a very ugly list of if-else and strncmp calls...
+
if(strncmp(message, "DeviceReady", messageLen) == 0)
{
syslog(LOG_DEBUG, "DeviceReady\n");
- if(SendCommand(fd, DeviceReadyCmd, 0) == 0)
+ if(SendCommand(fd, DeviceReadyCmd, NULL) == SUCCESS)
strncpy(retMessage, "OK\n", bufSize);
else
+ {
strncpy(retMessage, "ERR\n", bufSize);
+ return 1;
+ }
}
+
else if(strncmp(message, "GetTemperature", messageLen) == 0)
{
syslog(LOG_DEBUG, "GetTemperature\n");
- tmp = SendCommand(fd, ThermalStatusGetCmd, 1);
- tmp = ThermalTable[tmp];
- sprintf(retMessage, "%d", tmp);
- len = strlen(retMessage);
- retMessage[len] = '\n';
- retMessage[len+1] = '\0';
+ if(SendCommand(fd, ThermalStatusGetCmd, buf) > ERR_WRONG_ANSWER)
+ {
+ tmp = ThermalTable[(int)buf[5]];
+ snprintf(retMessage, bufSize, "%d", tmp);
+ len = strlen(retMessage);
+ if(bufSize > 1)
+ {
+ retMessage[len] = '\n';
+ retMessage[len+1] = '\0';
+ }
+ }
+ else
+ {
+ strncpy(retMessage, "ERR\n", bufSize);
+ return 1;
+ }
}
- else if(strncmp(message, "DeviceShutdown", strlen("DeviceShutdown")) == 0)
+
+/* else if(strncmp(message, "DeviceShutdown", strlen("DeviceShutdown")) == 0)
{
syslog(LOG_DEBUG, "DeviceShutdown");
if(messageLen >= (strlen("DeviceShutdown") + 2))
{
//tmp = atoi(&message[strlen("DeviceShutdown") + 1]); // FIXME: The parameter is never passed, we default to 10s here..
//printf("%s\n", tmp);
- if(SendCommand(fd, DeviceShutdownCmd, 0) == 0)
+ if(SendCommand(fd, DeviceShutdownCmd, NULL) == SUCCESS)
strncpy(retMessage, "OK\n", bufSize);
else
strncpy(retMessage, "ERR\n", bufSize);
}
- }
+ }*/
+
else if(strncmp(message, "quit", messageLen) == 0)
{
syslog(LOG_DEBUG, "Quit\n");
strncpy(retMessage, "Bye\n", bufSize);
- return 1;
+ return 2;
}
+
else if(strncmp(message, "EnablePowerRecovery", messageLen) == 0)
{
syslog(LOG_DEBUG, "EnablePowerRecovery");
- if(SendCommand(fd, APREnableCmd, 0) == 0)
+ if(SendCommand(fd, APREnableCmd, NULL) == SUCCESS)
strncpy(retMessage, "OK\n", bufSize);
else
+ {
strncpy(retMessage, "ERR\n", bufSize);
+ return 1;
+ }
}
+
else if(strncmp(message, "DisablePowerRecovery", messageLen) == 0)
{
syslog(LOG_DEBUG, "DisablePowerRecovery");
- if(SendCommand(fd, APRDisableCmd, 0) == 0)
+ if(SendCommand(fd, APRDisableCmd, NULL) == SUCCESS)
strncpy(retMessage, "OK\n", bufSize);
else
+ {
strncpy(retMessage, "ERR\n", bufSize);
+ return 1;
+ }
}
+
else if(strncmp(message, "GetPowerRecoveryState", messageLen) == 0)
{
syslog(LOG_DEBUG, "GetPowerRecoveryState");
- tmp = SendCommand(fd, APRStatusCmd, 1);
- sprintf(retMessage, "%d", tmp);
- len = strlen(retMessage);
- retMessage[len] = '\n';
- retMessage[len+1] = '\0';
+ if(SendCommand(fd, APRStatusCmd, buf) > ERR_WRONG_ANSWER)
+ {
+ snprintf(retMessage, bufSize, "%d", buf[5]);
+ len = strlen(retMessage);
+ if(bufSize > 1)
+ {
+ retMessage[len] = '\n';
+ retMessage[len+1] = '\0';
+ }
+ }
+ else
+ {
+ strncpy(retMessage, "ERR\n", bufSize);
+ return 1;
+ }
}
+
else if(strncmp(message, "EnableWOL", messageLen) == 0)
{
syslog(LOG_DEBUG, "EnableWOL");
- if(SendCommand(fd, WOLStatusEnableCmd, 0) == 0)
+ if(SendCommand(fd, WOLStatusEnableCmd, NULL) == SUCCESS)
strncpy(retMessage, "OK\n", bufSize);
else
+ {
strncpy(retMessage, "ERR\n", bufSize);
+ return 1;
+ }
}
+
else if(strncmp(message, "DisableWOL", messageLen) == 0)
{
syslog(LOG_DEBUG, "DisableWOL");
- if(SendCommand(fd, WOLStatusDisableCmd, 0) == 0)
+ if(SendCommand(fd, WOLStatusDisableCmd, NULL) == SUCCESS)
strncpy(retMessage, "OK\n", bufSize);
else
+ {
strncpy(retMessage, "ERR\n", bufSize);
+ return 1;
+ }
}
+
else if(strncmp(message, "GetWOLState", messageLen) == 0)
{
syslog(LOG_DEBUG, "GetWOLState");
- tmp = SendCommand(fd, WOLStatusGetCmd, 1);
- sprintf(retMessage, "%d", tmp);
- len = strlen(retMessage);
- retMessage[len] = '\n';
- retMessage[len+1] = '\0';
+ if(SendCommand(fd, WOLStatusGetCmd, buf) > ERR_WRONG_ANSWER)
+ {
+ snprintf(retMessage, bufSize, "%d", buf[5]);
+ len = strlen(retMessage);
+ if(bufSize > 1)
+ {
+ retMessage[len] = '\n';
+ retMessage[len+1] = '\0';
+ }
+ }
+ else
+ {
+ strncpy(retMessage, "ERR\n", bufSize);
+ return 1;
+ }
}
+
else if(strncmp(message, "PowerLedOn", messageLen) == 0)
{
syslog(LOG_DEBUG, "PowerLedOn");
- if(SendCommand(fd, PwrLedOnCmd, 0) == 0)
+ if(SendCommand(fd, PwrLedOnCmd, NULL) == SUCCESS)
strncpy(retMessage, "OK\n", bufSize);
else
+ {
strncpy(retMessage, "ERR\n", bufSize);
+ return 1;
+ }
}
+
else if(strncmp(message, "PowerLedOff", messageLen) == 0)
{
syslog(LOG_DEBUG, "PowerLedOff");
- if(SendCommand(fd, PwrLedOffCmd, 0) == 0)
+ if(SendCommand(fd, PwrLedOffCmd, NULL) == SUCCESS)
strncpy(retMessage, "OK\n", bufSize);
else
+ {
strncpy(retMessage, "ERR\n", bufSize);
+ return 1;
+ }
}
+
else if(strncmp(message, "PowerLedBlink", messageLen) == 0)
{
syslog(LOG_DEBUG, "PowerLedBlink");
- if(SendCommand(fd, PwrLedBlinkCmd, 0) == 0)
+ if(SendCommand(fd, PwrLedBlinkCmd, NULL) == SUCCESS)
strncpy(retMessage, "OK\n", bufSize);
else
+ {
strncpy(retMessage, "ERR\n", bufSize);
+ return 1;
+ }
}
+
else if(strncmp(message, "systohc", messageLen) == 0)
{
syslog(LOG_DEBUG, "systohc");
- strncpy(retMessage, "ERR\n", bufSize);
+ // Copy the command to our buffer
+ for(i=0;i<13;i++)
+ {
+ cmdBuf[i] = WDateAndTimeCmd[i];
+ }
+ sysTime = time(NULL);
+ strSetTime = localtime(&sysTime);
+ // Put the current local time into the command buffer
+ cmdBuf[5] = (char)strSetTime->tm_sec;
+ cmdBuf[6] = (char)strSetTime->tm_min;
+ cmdBuf[7] = (char)strSetTime->tm_hour;
+ cmdBuf[8] = (char)strSetTime->tm_wday;
+ cmdBuf[9] = (char)strSetTime->tm_mday;
+ cmdBuf[10] = (char)(strSetTime->tm_mon + 1);
+ cmdBuf[11] = (char)(strSetTime->tm_year - 100);
+ // And modify the values so that the MCU understands them...
+ for(i=5;i<12;i++)
+ {
+ cmdBuf[i] = ((cmdBuf[i] / 10) << 4) + (cmdBuf[i] % 10);
+ }
+ if(SendCommand(fd, cmdBuf, NULL) == SUCCESS)
+ strncpy(retMessage, "OK\n", bufSize);
+ else
+ {
+ strncpy(retMessage, "ERR\n", bufSize);
+ return 1;
+ }
}
+
else if(strncmp(message, "hctosys", messageLen) == 0)
{
syslog(LOG_DEBUG, "hctosys");
- strncpy(retMessage, "ERR\n", bufSize);
+ // Retrieve RTC time first
+ if(SendCommand(fd, RDateAndTimeCmd, buf) > ERR_WRONG_ANSWER)
+ {
+ for(i=5;i<12;i++)
+ {
+ buf[i] = (buf[i] & 0x0f) + 10 * ((buf[i] & 0xf0) >> 4); // The other end is a µC (doh!)
+ }
+
+ strTime.tm_year = (100 + (int)buf[11]);
+ strTime.tm_mon = buf[10]-1;
+ strTime.tm_mday = buf[9];
+ strTime.tm_hour = buf[7];
+ strTime.tm_min = buf[6];
+ strTime.tm_sec = buf[5];
+ strTime.tm_isdst = -1;
+ rtcTime = mktime(&strTime);
+ strcpy(timeStr, ctime(&rtcTime));
+ // Retrieve system time
+ sysTime = time(NULL);
+ setTime.tv_sec = rtcTime;
+ setTime.tv_usec = 0;
+ // Set the time and print the difference on success
+ if(settimeofday(&setTime, NULL) != 0)
+ strncpy(retMessage, "ERR\n", bufSize);
+ else
+ snprintf(retMessage, bufSize, "RTC: %sSys: %sDiff: %.fs\n", timeStr, ctime(&sysTime), difftime(sysTime, rtcTime));
+ }
+ else
+ {
+ strncpy(retMessage, "ERR\n", bufSize);
+ return 1;
+ }
}
+
+ else if(strncmp(message, "ReadRtc", messageLen) == 0)
+ {
+ syslog(LOG_DEBUG, "ReadRtc");
+ if(SendCommand(fd, RDateAndTimeCmd, buf) > ERR_WRONG_ANSWER)
+ {
+ for(i=5;i<12;i++)
+ {
+ buf[i] = (buf[i] & 0x0f) + 10 * ((buf[i] & 0xf0) >> 4); // The other end is a µC (doh!)
+ }
+ strTime.tm_year = (100 + (int)buf[11]);
+ strTime.tm_mon = buf[10]-1;
+ strTime.tm_mday = buf[9];
+ strTime.tm_hour = buf[7];
+ strTime.tm_min = buf[6];
+ strTime.tm_sec = buf[5];
+ strTime.tm_isdst = -1;
+ rtcTime = mktime(&strTime);
+ strcpy(timeStr, ctime(&rtcTime));
+ snprintf(retMessage, bufSize, "RTC: %s", timeStr);
+ }
+ else
+ {
+ strncpy(retMessage, "ERR\n", bufSize);
+ return 1;
+ }
+ }
+
+ else if(strncmp(message, "ShutdownDaemon", messageLen) == 0)
+ {
+ syslog(LOG_DEBUG, "ShutdownDaemon");
+ strncpy(retMessage, "OK\n", bufSize);
+ return 3;
+ }
+
else if(strncmp(message, "help", messageLen) == 0)
{
syslog(LOG_DEBUG, "help");
- strncpy(retMessage, "Available Commands: DeviceReady, GetTemperature, DeviceShutdown, "
+ strncpy(retMessage, "Available Commands: DeviceReady, GetTemperature, "// DeviceShutdown, "
"EnablePowerRecovery, DisablePowerRecovery, GetPowerRecoveryState, "
"EnableWOL, DisableWOL, GetWOLState, PowerLedOn, "
- "PowerLedOff, PowerLedBlink, quit\n", bufSize);
+ "PowerLedOff, PowerLedBlink, systohc, hctosys, ReadRtc, ShutdownDaemon, quit\n", bufSize);
}
else
{
strncpy(retMessage, "Command not Understood!\n", bufSize);
}
return 0;
}
-int main(int args, char *argv[])
+/** <i>Main Function</i>
+ @param argc The argument count
+ @param argv The argument vector
+ @return EXIT_SUCCESS on success, otherwise EXIT_ERROR
+*/
+int main(int argc, char *argv[])
{
- char *portname; // We hardcode the path, as this daemon is inteded to run on one box only
+ char *portname;
char *gpioDir;
int serverPort;
int fanPollTime;
int gpioPollTime;
char response[500];
int i;
+ pid_t pid;
+ pid_t sid;
int powerBtn;
int pressed;
int opt;
int sleepCount;
int pollTimeMs;
+ int goDaemon = 1;
+ int debug = 0;
+ int readRtcOnStartup = 0;
char buf[100];
+ char *configPath = "/etc/dns320l-daemon.ini";
+ char msgBuf[15];
int temperature;
int fanSpeed;
struct sockaddr_in s_name;
struct pollfd *fds = NULL;
nfds_t nfds;
int retval;
int ret;
int msgIdx;
int tempLow;
int tempHigh;
int hysteresis;
char message[500];
dictionary *iniFile;
socklen_t namelength;
pressed = 0;
nfds = 1;
opt = 1;
sleepCount = 0;
pollTimeMs = 10; // Sleep 10ms for every loop
fanSpeed = -1;
+ // Parse command line arguments
+ while((i = getopt(argc, argv, "fc:d")) != -1)
+ {
+ switch(i)
+ {
+ case 'f':
+ goDaemon = 0;
+ break;
+ case 'd':
+ debug = 1;
+ goDaemon = 0;
+ break;
+ case 'c':
+ configPath = optarg;
+ break;
+ case '?':
+ if(optopt == 'c')
+ fprintf(stderr, "Option -%c requires an argument.\n", optopt);
+ else if (isprint (optopt))
+ fprintf (stderr, "Unknown option `-%c'.\n", optopt);
+ else
+ fprintf (stderr,
+ "Unknown option character `\\x%x'.\n",
+ optopt);
+ fprintf(stderr, "Usage: %s [-f] [-c configPath] [-d]\n", argv[0]);
+ fprintf(stderr, " where\n");
+ fprintf(stderr, " -f don't detach\n");
+ fprintf(stderr, " -c configPath path to .ini\n");
+ fprintf(stderr, " -d debug (implies -f)\n");
+ return EXIT_FAILURE;
+ }
+
+ }
+
+ // Register some signal handlers
signal(SIGTERM, sighandler);
signal(SIGINT, sighandler);
-
-
- iniFile = iniparser_load("/etc/dns320l-daemon.ini");
+
+ // Load our configuration file or use default values
+ // if it doesn't exist!
+ iniFile = iniparser_load(configPath);
portname = iniparser_getstring(iniFile, "Serial:Port", "/dev/ttyS1");
+ readRtcOnStartup = iniparser_getint(iniFile, "Daemon:SyncTimeOnStartup", 0);
fanPollTime = iniparser_getint(iniFile, "Fan:PollTime", 15);
tempLow = iniparser_getint(iniFile, "Fan:TempLow", 45);
tempHigh = iniparser_getint(iniFile, "Fan:TempHigh", 50);
hysteresis = iniparser_getint(iniFile, "Fan:Hysteresis", 2);
gpioPollTime = iniparser_getint(iniFile, "GPIO:PollTime", 1);
gpioDir = iniparser_getstring(iniFile, "GPIO:SysfsGpioDir", "/sys/class/gpio");
serverPort = iniparser_getint(iniFile, "Daemon:ServerPort", 57367);
+ // Setup syslog
+ if(debug)
+ setlogmask(LOG_UPTO(LOG_DEBUG));
+ else
+ setlogmask(LOG_UPTO(LOG_INFO));
+
+ if(goDaemon)
+ openlog("dns320l-daemon", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
+ else
+ openlog("dns320l-daemon", LOG_CONS | LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_LOCAL1);
+
+ if(goDaemon)
+ {
+ pid = fork();
+ if(pid < 0)
+ {
+ syslog(LOG_ERR, "Forking failed.\n");
+ return EXIT_FAILURE;
+ }
+
+ if(pid > 0)
+ {
+ return EXIT_SUCCESS;
+ }
+ // From here on we are the child process...
+ umask(0);
+ sid = setsid();
+ if(sid < 0)
+ {
+ syslog(LOG_ERR, "Could not create process group\n");
+ return EXIT_FAILURE;
+ }
+
+ if((chdir("/")) < 0)
+ {
+ syslog(LOG_ERR, "Could not chdir(\"/\")\n");
+ return EXIT_FAILURE;
+ }
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+
+ }
- setlogmask(LOG_UPTO(LOG_DEBUG));
- openlog("dns320l-daemon", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
+
+
+ // Open our socket server
if ((ls = socket (AF_INET, SOCK_STREAM, 0)) == -1){
syslog(LOG_ERR, "socket");
exit(EXIT_FAILURE);
}
if (setsockopt(ls,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof opt)<0){
syslog(LOG_ERR, "setsockopt (SO_RESUSEADDR): %s\r\n",strerror(errno));
exit(EXIT_FAILURE);
}
s_name.sin_family = AF_INET;
s_name.sin_port = htons(serverPort);
s_name.sin_addr.s_addr = htonl(INADDR_ANY);
syslog(LOG_DEBUG, "Bind name to ls. \n");
retval = bind (ls,(struct sockaddr *)&s_name, sizeof s_name);
if (retval)
{
syslog(LOG_ERR, "bind");
cleanup(0, ls,1);
exit(EXIT_FAILURE);
}
syslog(LOG_DEBUG, "Listen on ls for connections. \n");
retval = listen (ls, 5);
if (retval)
{
syslog(LOG_ERR, "listen");
cleanup(0, ls,1);
exit(EXIT_FAILURE);
}
syslog(LOG_INFO, "Server startup success on port %i\n", serverPort);
fds = (struct pollfd *)calloc(1,nfds*sizeof(struct pollfd));
fds->fd = ls;
fds->events = POLLIN | POLLPRI;
fd = open (portname, O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0)
{
syslog(LOG_ERR, "error %d opening %s: %s", errno, portname, strerror (errno));
return;
}
set_interface_attribs (fd, B115200, 0); // set speed to 115,200 bps, 8n1 (no parity)
set_blocking (fd, 0); // set no blocking
// Flush the serial port first...
read(fd, buf, 100);
-
- if(SendCommand(fd, DeviceReadyCmd, 0) == 0)
+ // Send the DeviceReady command to the MCU
+ if(SendCommand(fd, DeviceReadyCmd, NULL) == SUCCESS)
syslog(LOG_INFO, "dns320l-daemon startup complete, going to FanControl mode");
else
{
syslog(LOG_ERR, "Error sending DeviceReady command, exit!\n");
return EXIT_FAILURE;
}
+
+ if(readRtcOnStartup)
+ {
+ syslog(LOG_INFO, "Setting system clock from RTC...\n");
+ if(HandleCommand("hctosys", 7, NULL, 0) != 0)
+ syslog(LOG_ERR, "Error setting system time from RTC!\n");
+ }
+ // Go to endless loop and do the following:
+ // Get the thermal status
+ // Check temperature and adjust fan speeds
+ // Wake every 1s to poll the power button GPIO
+ // Wake every few ms to poll the sockets for connections
+ // Sleep
+
while(1)
{
sleepCount = 0;
- temperature = SendCommand(fd, ThermalStatusGetCmd, 1);
+ if(SendCommand(fd, ThermalStatusGetCmd, msgBuf) > ERR_WRONG_ANSWER)
+ temperature = msgBuf[5];
+ else
+ temperature = 0;
if(temperature > 0)
{
temperature = ThermalTable[temperature];
syslog(LOG_DEBUG, "Read Temperature: %i\n", temperature);
if(temperature < (tempLow - hysteresis))
{
if(fanSpeed != 0)
{
syslog(LOG_DEBUG, "Set Fan Stop\n");
- SendCommand(fd, FanStopCmd, 0);
+ SendCommand(fd, FanStopCmd, NULL);
fanSpeed = 0;
}
}
else if(temperature < tempLow)
{
if(fanSpeed > 1)
{
syslog(LOG_DEBUG, "Set Fan Half\n");
- SendCommand(fd, FanHalfCmd, 0);
+ SendCommand(fd, FanHalfCmd, NULL);
fanSpeed = 1;
}
}
else if(temperature < (tempHigh - hysteresis))
{
if(fanSpeed != 1)
{
syslog(LOG_DEBUG, "Set Fan Half\n");
- SendCommand(fd, FanHalfCmd, 0);
+ SendCommand(fd, FanHalfCmd, NULL);
fanSpeed = 1;
}
}
else if(temperature < tempHigh)
{
if(fanSpeed < 1)
{
syslog(LOG_DEBUG, "Set Fan Half\n");
- SendCommand(fd, FanHalfCmd, 0);
+ SendCommand(fd, FanHalfCmd, NULL);
fanSpeed = 1;
}
}
else
{
if(fanSpeed != 2)
{
syslog(LOG_DEBUG, "Set Fan Full\n");
- SendCommand(fd, FanFullCmd, 0);
+ SendCommand(fd, FanFullCmd, NULL);
fanSpeed = 2;
}
}
}
else
{
syslog(LOG_ERR, "Error reading Temperature!\n");
}
while((sleepCount * pollTimeMs) < (fanPollTime * 1000))
{
if(((sleepCount * pollTimeMs) % (gpioPollTime* 1000)) == 0)
{
if(gpio_get_value(GPIO_BUTTON_POWER, &powerBtn, gpioDir) == 0)
{
if((powerBtn == 0) && !pressed)
{
pressed = 1;
syslog(LOG_INFO, "Power Button Pressed, shutting down system!\n");
- SendCommand(fd, DeviceShutdownCmd, 0);
+ SendCommand(fd, DeviceShutdownCmd, NULL);
execl("/sbin/shutdown", "shutdown", "-h", "now", (char *)0);
}
}
}
sleepCount++;
ret=poll(fds,nfds,pollTimeMs); // Time out after pollTimeMs
if (ret == -1){
syslog(LOG_ERR, "poll");
exit(EXIT_FAILURE);
}
for (i=0;(i<nfds) && (ret);i++)
{
if (!(fds+i)->revents)
continue;
ret--;
if (((fds+i)->fd == ls) && ((fds+i)->revents & POLLIN))
{
/*
* Accept connection from socket ls:
* accepted connection will be on socket (fds+nfds)->fd.
*/
syslog(LOG_DEBUG, "POLLIN on ls. Accepting connection\n");
namelength = sizeof (s_name);
fds = (struct pollfd *)realloc(fds,(nfds+1)*sizeof(struct pollfd));
(fds+nfds)->fd = accept (ls, (struct sockaddr *)&s_name, &namelength);
if ((fds+nfds)->fd == -1)
{
syslog(LOG_ERR, "accept");
cleanup(0, (fds+nfds)->fd, 1);
fds = (struct pollfd *)realloc(fds,nfds*sizeof(struct pollfd));
continue;
}
(fds+nfds)->events = POLLIN | POLLRDNORM;
nfds++;
continue;
}
if ((fds+i)->revents & POLLNVAL)
{
syslog(LOG_DEBUG, "POLLNVAL on socket. Freeing resource\n");
nfds--;
memcpy(fds+i,fds+i+1,nfds-i);
fds = (struct pollfd *)realloc(fds,nfds*sizeof(struct pollfd));
continue;
}
if ((fds+i)->revents & POLLHUP)
{
syslog(LOG_DEBUG, "POLLHUP => peer reset connection ...\n");
cleanup(0,(fds+i)->fd,2);
nfds--;
memcpy(fds+i,fds+i+1,nfds-i);
fds = (struct pollfd *)realloc(fds,nfds*sizeof(struct pollfd));
continue;
}
if ((fds+i)->revents & POLLERR){
syslog(LOG_DEBUG, "POLLERR => peer reset connection ...\n");
cleanup(0,(fds+i)->fd,2);
nfds--;
memcpy(fds+i,fds+i+1,nfds-i);
fds = (struct pollfd *)realloc(fds,nfds*sizeof(struct pollfd));
continue;
}
if ((fds+i)->revents & POLLRDNORM)
{
retval = recv((fds+i)->fd, message, sizeof(message)-1, 0); // Don't forget the string terminator here!
syslog(LOG_DEBUG, "-> (recv) retval = %d.\n",retval); /* ped */
msgIdx = retval;
if (retval <=0)
{
if (retval == 0)
{
syslog(LOG_DEBUG, "recv()==0 => peer disconnected...\n");
cleanup(1,(fds+i)->fd,2);
}
else
{
syslog(LOG_ERR, "receive");
cleanup( 0, (fds+i)->fd,1);
}
nfds--;
memcpy(fds+i,fds+i+1,nfds-i);
fds = (struct pollfd *)realloc(fds,nfds*sizeof(struct pollfd));
continue;
}
while((retval > 0) && (message[msgIdx-2] != '\r') && ((msgIdx+1) < sizeof(message)))
{
retval = recv((fds+i)->fd, &message[msgIdx-2], sizeof(message) - retval - 1, 0);
syslog(LOG_DEBUG, " \t -> (recv) retval = %d.\n", retval);
if(retval > 0)
msgIdx += retval - 2;
}
if(msgIdx > 1)
if(message[msgIdx-1] == '\n')
if(message[msgIdx-2] == '\r')
message[msgIdx-2] = '\0';
else
message[msgIdx-1] = '\0';
syslog(LOG_DEBUG, "Normal message : %.*s\n",retval,message);
msgIdx = HandleCommand(message, msgIdx, response, sizeof(response));
retval = send((fds+i)->fd, response, strlen(response), 0);
- if((retval < 0) || (msgIdx == 1))
+ if((retval < 0) || (msgIdx > 1))
{
syslog(LOG_DEBUG, "send()==0 => peer disconnected...\n");
cleanup(1,(fds+1)->fd, 2);
}
+ if(msgIdx == 3)
+ {
+ syslog(LOG_INFO, "Shutting down dns320l-daemon...\n");
+ return EXIT_SUCCESS;
+ }
continue;
}
}
}
}
+ closelog();
iniparser_freedict(iniFile);
- return 0;
+ return EXIT_SUCCESS;
}
diff --git a/dns320l-daemon.ini b/dns320l-daemon.ini
new file mode 100644
--- /dev/null
+++ b/dns320l-daemon.ini
@@ -0,0 +1,16 @@
+[Serial]
+Port = /dev/ttyS1
+
+[Daemon]
+ServerPort = 57367
+SyncTimeOnStartup = 0
+
+[GPIO]
+SysfsGpioDir = /sys/class/gpio
+PollTime = 1
+
+[Fan]
+PollTime = 15
+TempLow = 45
+TempHigh = 50
+Hysteresis = 2
diff --git a/dns320l-daemon.service b/dns320l-daemon.service
new file mode 100644
--- /dev/null
+++ b/dns320l-daemon.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=DNS320L System Daemon
+After=syslog.target
+
+[Service]
+ExecStart=/usr/bin/dns320l-daemon
+Type=forking
+
+[Install]
+WantedBy=multi-user.target
diff --git a/dns320l.h b/dns320l.h
--- a/dns320l.h
+++ b/dns320l.h
@@ -1,48 +1,71 @@
#ifndef DNS320L_H
#define DNS320L_H
+/*
+
+Simple system daemon for D-Link DNS-320L
+
+(c) 2013 Andreas Boehler, andreas _AT_ aboehler.at
+
+This program is free software: you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
#define ERR_WRONG_ANSWER -1
+#define SUCCESS 1
#define CMD_START_MAGIC 0xfa
#define CMD_STOP_MAGIC 0xfb
#define GPIO_BUTTON_POWER 29
-const char DeviceReadyCmd[] = {0xfa, 0x03, 0x01, 0x00, 0x00, 0x00, 0xfb};
-const char AckFromSerial[] = {0xfa, 0x30, 0x00, 0x00, 0x00, 0x00, 0xfb};
-const char ThermalStatusGetCmd[] = {0xfa, 0x03, 0x08, 0x00, 0x00, 0x00, 0xfb};
-const char FanStopCmd[] = {0xfa, 0x02, 0x00, 0x00, 0x00, 0x00, 0xfb};
-const char FanHalfCmd[] = {0xfa, 0x02, 0x00, 0x01, 0x00, 0x00, 0xfb};
-const char FanFullCmd[] = {0xfa, 0x02, 0x00, 0x02, 0x00, 0x00, 0xfb};
-const char DeviceShutdownCmd[] = {0xfa, 0x03, 0x03, 0x01, 0x01, 0x0a, 0xfb};
-const char APREnableCmd[] = {0xfa, 0x03, 0x02, 0x01, 0x00, 0x00, 0xfb};
-const char APRDisableCmd[] = {0xfa, 0x03, 0x02, 0x00, 0x00, 0x00, 0xfb};
-const char APRStatusCmd[] = {0xfa, 0x03, 0x02, 0x02, 0x00, 0x00, 0xfb};
-const char PwrLedOnCmd[] = {0xfa, 0x03, 0x06, 0x01, 0x00, 0x01, 0xfb};
-const char PwrLedOffCmd[] = {0xfa, 0x03, 0x06, 0x00, 0x00, 0x01, 0xfb};
-const char PwrLedBlinkCmd[] = {0xfa, 0x03, 0x06, 0x02, 0x00, 0x01, 0xfb};
-const char WOLStatusEnableCmd[] = {0xfa, 0x03, 0x0a, 0x01, 0x00, 0x00, 0xfb};
-const char WOLStatusDisableCmd[] = {0xfa, 0x03, 0x0a, 0x00, 0x00, 0x00, 0xfb};
-const char WOLStatusGetCmd[] = {0xfa, 0x03, 0x0a, 0x02, 0x00, 0x00, 0xfb};
+char DeviceReadyCmd[] = {0xfa, 0x03, 0x01, 0x00, 0x00, 0x00, 0xfb};
+char AckFromSerial[] = {0xfa, 0x30, 0x00, 0x00, 0x00, 0x00, 0xfb};
+char ThermalStatusGetCmd[] = {0xfa, 0x03, 0x08, 0x00, 0x00, 0x00, 0xfb};
+char FanStopCmd[] = {0xfa, 0x02, 0x00, 0x00, 0x00, 0x00, 0xfb};
+char FanHalfCmd[] = {0xfa, 0x02, 0x00, 0x01, 0x00, 0x00, 0xfb};
+char FanFullCmd[] = {0xfa, 0x02, 0x00, 0x02, 0x00, 0x00, 0xfb};
+char DeviceShutdownCmd[] = {0xfa, 0x03, 0x03, 0x01, 0x01, 0x0a, 0xfb};
+char APREnableCmd[] = {0xfa, 0x03, 0x02, 0x01, 0x00, 0x00, 0xfb};
+char APRDisableCmd[] = {0xfa, 0x03, 0x02, 0x00, 0x00, 0x00, 0xfb};
+char APRStatusCmd[] = {0xfa, 0x03, 0x02, 0x02, 0x00, 0x00, 0xfb};
+char PwrLedOnCmd[] = {0xfa, 0x03, 0x06, 0x01, 0x00, 0x01, 0xfb};
+char PwrLedOffCmd[] = {0xfa, 0x03, 0x06, 0x00, 0x00, 0x01, 0xfb};
+char PwrLedBlinkCmd[] = {0xfa, 0x03, 0x06, 0x02, 0x00, 0x01, 0xfb};
+char WOLStatusEnableCmd[] = {0xfa, 0x03, 0x0a, 0x01, 0x00, 0x00, 0xfb};
+char WOLStatusDisableCmd[] = {0xfa, 0x03, 0x0a, 0x00, 0x00, 0x00, 0xfb};
+char WOLStatusGetCmd[] = {0xfa, 0x03, 0x0a, 0x02, 0x00, 0x00, 0xfb};
+char RDateAndTimeCmd[] = {0xfa, 0x01, 0x08, 0x01, 0x01, 0x00, 0xfb};
+char WDateAndTimeCmd[] = {0xfa, 0x01, 0x08, 0x02, 0x07, 0x17, 0x06, 0x21, 0x02, 0x10, 0x09, 0x13, 0xfb};
const char ThermalTable[] = {0x74, 0x73, 0x72, 0x71, 0x70, 0x6F, 0x6E, 0x6D, 0x6C, 0x6B,
0x6A, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61,
0x60, 0x5F, 0x5E, 0x5D, 0x5C, 0x5B, 0x5A, 0x59, 0x58, 0x57,
0x56, 0x55, 0x54, 0x53, 0x52, 0x51, 0x50, 0x4F, 0x4E, 0x4D,
0x4C, 0x4B, 0x4A, 0x49, 0x48, 0x47, 0x46, 0x45, 0x44, 0x43,
0x42, 0x41, 0x41, 0x40, 0x3F, 0x3E, 0x3E, 0x3D, 0x3D, 0x3C,
0x3B, 0x3A, 0x3A, 0x39, 0x38, 0x38, 0x37, 0x36, 0x36, 0x35,
0x34, 0x34, 0x33, 0x33, 0x32, 0x31, 0x31, 0x30, 0x30, 0x2F,
0x2F, 0x2E, 0x2E, 0x2D, 0x2C, 0x2C, 0x2B, 0x2B, 0x2A, 0x2A,
0x29, 0x29, 0x28, 0x28, 0x27, 0x27, 0x27, 0x26, 0x26, 0x25,
0x25, 0x24, 0x24, 0x23, 0x23, 0x22, 0x22, 0x21, 0x21, 0x21,
0x20, 0x20, 0x1F, 0x1F, 0x1E, 0x1E, 0x1E, 0x1D, 0x1D, 0x1C,
0x1C, 0x1B, 0x1B, 0x1B, 0x1B, 0x1A, 0x19, 0x19, 0x19, 0x18,
0x18, 0x17, 0x17, 0x25, 0x1B, 0x1B, 0x19, 0x19, 0x19, 0x18,
0x18, 0x17, 0x17, 0x16, 0x16, 0x16, 0x15, 0x15, 0x14, 0x14,
0x14, 0x13, 0x13, 0x12, 0x12, 0x12, 0x11, 0x11, 0x10, 0x10,
0x10, 0xF, 0xF, 0xE, 0xE, 0xE, 0xD, 0xD, 0xC, 0xC, 0xC, 0xB,
0xB, 0xA, 0xA, 9, 9, 9, 8, 8, 7, 7, 7, 6, 6, 5, 5, 4, 4, 4, 3,
3, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0};
#endif //DNS320L_H

File Metadata

Mime Type
text/x-diff
Expires
Fri, Jan 24, 3:20 AM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
532472
Default Alt Text
(39 KB)

Event Timeline