Raumklima
hallo zusammen, da ich jetzt schon merhfach immer wieder gefragt worden bin nach dem programm, womit auto 1w sensor detection möglich ist, hier ist es mal :-)
// ################################################################################
// # #
// # Daten Analyse und Programm Erstellung: #
// # Copyright (c) 2009-2010 - Michael Schultz <ethersex@keyb.de> #
// # Mit viel unterstuetzung/Geduld von: stesie, veyron und DanielW #
// # #
// # This program is free software; you can redistribute it and/or modify it #
// # under the terms of the GNU General Public License (either version 2 or #
// # version 3) as published by the Free Software Foundation. #
// # #
// # 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, write to the Free Software #
// # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #
// # #
// # For more information on the GPL, please go to: #
// # http://www.gnu.org/copyleft/gpl.html #
// # #
// # #
// # Wetter-Funk-Daten Analyse Programm für Wetter Station der Firma #
// # Krippl Watches GmbH -- http://www.produktservice.info #
// # Protokoll analyse siehe: #
// # http://www.ethersex.de/index.php/Funk-wetterstations-protokoll #
// # #
// ################################################################################
// Logging Destination Host: 10.0.0.77 on Port 95
#ifndef ONEWIRE_SUPPORT
#error Please define onewire support
#endif
#ifdef ONEWIRE_DETECT_SUPPORT
#error Please undefine onewire detect support
#endif
#define PRESCALER 1024
#define NS_PER_TICK (PRESCALER * 1000000UL / F_CPU * 1000)
#define US_TO_TICKS(n) ((n) * 1000UL / NS_PER_TICK)
uint16_t Count1;
uint16_t Count2;
// Timing @16mhz: Timer Count Timer C. Hex Decode
uint8_t Time0 = US_TO_TICKS(1920); // L space 1920 bis 1999us 30-31,2 0x1E 0x1F 0
uint8_t Time1 = US_TO_TICKS(3990); // H space 3990 bis 4049us 62,3-63,2 0x3E 0x3F 1
uint8_t Time2 = US_TO_TICKS(2060); // End-l space 2060 bis 2089us 32,1-32,6 0x20 0x21 2
uint8_t Time3 = US_TO_TICKS(4130); // End-h space 4130 bis 4169us 64,5-65,1 0x40 0x41 3
uint8_t Time4 = US_TO_TICKS(8890); // Start space 8890 bis 8939us 138,9-139,6 0x8A 0x8C 4
uint8_t Time5 = US_TO_TICKS(360); // End- pulse 360 bis 369us 5,6-5,7 0x05 5
uint8_t Time6 = US_TO_TICKS(440); // _ pulse 440 bis 519us 6,8-8,1 0x06 0x08 6
// Pause
//
uint8_t BitPos = 0; // Bit Position innerhalb eines 36 bit worts (1 bis 37)
uint8_t WordCound = 0; // Daten Wort Count (1 bis 9)
uint8_t WordByte[50]; // 1 Data Word = 5 Byte / Max 8 Data Words
uint8_t WordBytePos = 0; // Byte Pos in WordByte
uint8_t MatchPos = 0; // meist vorkommene datenwort position im telegram
uint8_t MatchCount = 0; // wie viel treffer hatte dieses meist vorkommende telegram
//
int16_t Temp; // Temperatur Wert (-)76,8 bis (+)127,9 °C, Vorzeichen wird erst bei der ausgabe gesetzt.
uint16_t Feuchte = 0; // Luftfeuchte Wert 20-99 %
uint8_t timer_0 = 0; // Timer Wert für zeitbestimming zwichen 2 Pegel änderungen (Interrupts)
//
uint8_t Analysis[10] ; // Daten Analyse array
dnl uint8_t Analysis[15] ; // Daten Analyse array
// Analysis[0 bis 7] Treffer der Daten Packets Übereinstimmung.
// Analysis[8] - bit(0) = 1 Daten Liegen an und können vearbeitete werden
// Analysis[8] - bit(1) = 1 Datenanalyse erfolgreich, Mindestens 3 Übereinstimmungen
// Analysis[8] - bit(2) = 1 Daten sind Verarbeitet und können gesendet werden
// Analysis[9] Array Positions Counter fuer analyse
uint16_t InterruptCall = 0 ; // Interrupt0 aufrufs counter
uint16_t InterruptCallOld = 0 ;
uint8_t OW_RomAddrLst[128] ; // Maximum 16 Sensors to find
uint8_t OW_SensorsFound;
uint8_t Motor1Pwm ;
uint8_t Motor2Pwm ;
uint8_t Motor1PwmCount = 0;
uint8_t Motor2PwmCount = 0;
uint8_t OWSend;
uint16_t OWGetTimer = 0;
int16_t OWTemp;
uint8_t OWSendSensor;
uint8_t OWSendSensorOld;
uint8_t Channel;
uint16_t NetWatchDog = 0;
uint8_t ManuSend;
uint8_t MessageCnt = 0;
uint16_t SendMessage = 0;
uint16_t MarkSending = 0;
uint16_t MarkCnt = 0;
dnl uint8_t reset;
dnl char YearMonth[13][4] = {"---", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
//########################################################################################################################
#include "hardware/onewire/onewire.h"
#include "core/bit-macros.h"
void noinline ow_set_address_bit(struct ow_rom_code_t *rom, uint8_t idx, uint8_t val);
int8_t noinline ow_search_rom(uint8_t first) {
/* reset discover state machine */
if (first) {
ow_global.last_discrepancy = -1;
/* reset rom code */
for (uint8_t i = 0; i < 8; i++) { ow_global.current_rom.bytewise[i] = 0; }
} else {
/* if last_discrepancy is below zero, discovery is done */
if (ow_global.last_discrepancy < 0) { return 0; }
}
uint8_t discrepancy = -1;
if (!reset_onewire()) { return -1; } /* reset the bus */
ow_write_byte(OW_ROM_SEARCH_ROM); /* transmit command byte */
for (uint8_t i = 0; i <64; i++) {
uint8_t bit1 = ow_read(); /* read bits */
uint8_t bits = (ow_read() << 1) | bit1;
if (bits == 3) {
return 0; /* no devices, just return */
} else if (bits == 0) {
if (i == ow_global.last_discrepancy) {
ow_set_address_bit(&ow_global.current_rom, i, 1); /* set one */
bit1 = 1; /* transmit one next time */
} else if (i > ow_global.last_discrepancy) {
ow_set_address_bit(&ow_global.current_rom, i, 0); /* set zero */
discrepancy = i;
} else {
uint8_t rom_bit = ow_global.current_rom.bytewise[i / 8] & _BV(i % 8);
if (rom_bit == 0)
discrepancy = i;
bit1 = rom_bit; /* transmit last bit next time */
}
} else {
ow_set_address_bit(&ow_global.current_rom, i, bit1); /* normal case, no discrepancy */
}
OW_CONFIG_OUTPUT();
ow_write(bit1); /* select next bit */
}
ow_global.last_discrepancy = discrepancy;
return 1; /* new device discovered */
}
void save_onewire_list() {
OW_SensorsFound=0;
for (uint8_t i = 0; i < (129); i++) {
OW_RomAddrLst[i] = 0 ;
}
int16_t ret;
onewire_list_loop:
if (ow_global.lock == 0) {
/* make sure only one conversion happens at a time */
ow_global.lock = 1;
uint8_t sreg = SREG; /* disable interrupts */
cli();
ret = ow_search_rom_first();
SREG = sreg; /* re-enable interrupts */
dnl if (ret <= 0) { /* no devices on the bus */
dnl OW_SensorsFound=0;
dnl }
} else {
uint8_t sreg = SREG; /* disable interrupts */
cli();
ret = ow_search_rom_next();
SREG = sreg; /* re-enable interrupts */
}
if (ret == 1) {
for (uint8_t i = 0; i < 8; i++) {
OW_RomAddrLst[((OW_SensorsFound * 8) + i )] = ow_global.current_rom.bytewise[i] ;
}
OW_SensorsFound ++;
goto onewire_list_loop;
} else if (ret == 0) {
ow_global.lock = 0;
}
}
int16_t ow_read_temp (struct ow_rom_code_t *rom)
{
int16_t retval = 0x7FFF; /* error */
uint8_t sreg = SREG; /* disable interrupts */
cli();
struct ow_temp_scratchpad_t sp;
if (ow_temp_read_scratchpad(rom, &sp) != 1)
goto out1; // scratchpad read failed
uint16_t temp = ow_temp_normalize(rom, &sp);
uint8_t sign = ((int16_t) temp < 0);
if (sign) { temp = -temp; }
retval = HI8(temp) * 10 + HI8(((temp & 0x00ff) * 10) + 0x80);
if (sign) { retval = retval * - 1; }
out1:
SREG = sreg; /* re-enable interrupts */
return retval;
}
int16_t ow_temp (struct ow_rom_code_t *rom)
{
int16_t retval = 0x7FFF; /* error */
uint8_t sreg = SREG; /* disable interrupts */
cli();
if (ow_temp_start_convert_wait(rom) != 1)
goto out2;
retval=ow_read_temp(rom);
out2:
SREG = sreg; /* re-enable interrupts */
return retval;
}
//########################################################################################################################
dnl /* jump to bootloader */
dnl void (*jump_boot)(void) = (void *)BOOTLOADER_SECTION ;
#include "protocols/ecmd/ecmd-base.h"
dnl int16_t parse_cmd_res(char *cmd, char *output, uint16_t len) {
dnl // status.request_reset = 1;
dnl reset=1;
dnl return ECMD_FINAL_OK;
dnl }
int16_t parse_cmd_nwd(char *cmd, char *output, uint16_t len) {
NetWatchDog = 0;
return ECMD_FINAL_OK;
}
int16_t parse_cmd_m1on(char *cmd, char *output, uint16_t len) {
while (*cmd == ' ')
cmd++;
if (*cmd == '\0') {
Motor1Pwm = 1;
return ECMD_FINAL_OK;
} else {
uint8_t NewMotor1Pwm = strtoul(cmd, NULL, 10);
if (!NewMotor1Pwm)
return ECMD_ERR_PARSE_ERROR;
Motor1Pwm=NewMotor1Pwm;
return ECMD_FINAL_OK;
}
}
int16_t parse_cmd_m1off(char *cmd, char *output, uint16_t len) {
PORTC &= ~(1 << PC3); //
Motor1Pwm = 0;
return ECMD_FINAL_OK;
}
int16_t parse_cmd_m2on(char *cmd, char *output, uint16_t len) {
while (*cmd == ' ')
cmd++;
if (*cmd == '\0') {
Motor2Pwm = 1;
return ECMD_FINAL_OK;
} else {
uint8_t NewMotor2Pwm = strtoul(cmd, NULL, 10);
if (!NewMotor2Pwm)
return ECMD_ERR_PARSE_ERROR;
Motor2Pwm=NewMotor2Pwm;
return ECMD_FINAL_OK;
}
}
int16_t parse_cmd_m2off(char *cmd, char *output, uint16_t len) {
PORTC &= ~(1 << PC5); //
Motor2Pwm = 0;
return ECMD_FINAL_OK;
}
int16_t parse_cmd_charge(char *cmd, char *output, uint16_t len) {
PORTA &= ~(1 << PA7);
return ECMD_FINAL_OK;
}
int16_t parse_cmd_discharge(char *cmd, char *output, uint16_t len) {
PORTA |= (1 << PA7);
return ECMD_FINAL_OK;
}
int16_t parse_cmd_owrl(char *cmd, char *output, uint16_t len) {
save_onewire_list();
return ECMD_FINAL_OK;
}
//########################################################################################################################
CONTROL_START
ON STARTUP DO
Analysis[8] |= (1 << 2); // nach start einmal ausgabe, um zu schauen wann das ding gebootet ist...
_TCCR0_PRESCALE |= _BV(CS02); // TCCR0B(CS02)=1
_TCCR0_PRESCALE &= ~_BV(CS01); // TCCR0B(CS01)=0
_TCCR0_PRESCALE |= _BV(CS00); // TCCR0B(CS00)=1
DDRD &= ~(1 << DDD2); //
PORTD |= (1 << PD2); //
MCUCR &= ~(1 << PUD); //
DDRC |= (1 << DDC3); //
DDRC |= (1 << DDC5); //
DDRA |= (1 << DDA7); //
// PORTA &= ~(1 << PA7);
PORTA |= (1 << PA7); // discharge the accu after reboot
// PORTD &= ~(1 << DDD0); // USART RX PIN Inout
// PORTD |= (1 << PD0); // USART RX PIN
// PORTD |= (1 << DDD1); // USART RX PIN Output
// PORTD |= (1 << PD1); // USART TX PIN
// PD1/TXD PD0/RXD
// PUOE TXEN RXEN
// PUOV 0 PORTD0 · PUD
// DDOE TXEN RXEN
//
// s50
_EIMSK |= _BV(INT0); // EIMSK(INT0) = 1 Interrupt, Input Data on INT0 / PD2
_EICRA &= ~_BV(ISC01); // EICRA(ISC01) = 0 interrupt on any edge, asyncronously
_EICRA |= _BV(ISC00); // EICRA(ISC00) = 1 interrupt on any edge, asyncronously
_TIMSK_TIMER0 |= _BV(TOIE0); // TIMSK0(TOIE0) = 1 Interrupt on Timer Overflow aktivieren
TCP_CONNECT(10.0.0.77, 95, message_handler);
save_onewire_list();
END
if ( Motor1Pwm != 0 ) {
Motor1PwmCount ++;
PORTC |= (1 << PC3);
if ( Motor1PwmCount >= Motor1Pwm ) {
PORTC &= ~(1 << PC3);
}
if ( Motor1PwmCount >= 5 ) { Motor1PwmCount = 0; }
}
if ( Motor2Pwm != 0 ) {
Motor2PwmCount ++;
PORTC |= (1 << PC5);
if ( Motor2PwmCount >= Motor2Pwm ) {
PORTC &= ~(1 << PC5);
}
if ( Motor2PwmCount >= 5 ) { Motor2PwmCount = 0; }
}
TCP_HANDLER_PERSIST(message_handler)
for (;;) {
if ( SendMessage >= 50 ) {
SendMessage = 0;
InterruptCallOld = InterruptCall;
MessageCnt ++;
// Lösche ale Analysis Counter
for (Count1 = 0; Count1 < 8; Count1 ++) Analysis[ (Count1) ] = 0 ;
// wie oft matcht welsches telegram mit allen anderen 8 telegrammen
for (Count1 = 0; Count1 < 8; Count1 ++)
for (Count2 = 0; Count2 < 8; Count2 ++)
if (memcmp(&WordByte[ (Count1) * 5 ], &WordByte[ ( Count2 + 1 ) * 5], 5) == 0)
{ Analysis[ (Count1) ] ++ ; }
// nim nur das telegram mit der größten anzahl an treffer
for (Count1 = 8; Count1 > 1; Count1 --)
for (Count2 = 0; Count2 < 8; Count2 ++)
if ( Analysis[ (Count2) ] == Count1 ) {
MatchPos = Count2 ;
MatchCount = Count1 ;
WordByte[45] = WordByte[ ( MatchPos * 5 ) ] ;
WordByte[46] = WordByte[ ( MatchPos * 5 ) + 1 ] ;
WordByte[47] = WordByte[ ( MatchPos * 5 ) + 2 ] ;
WordByte[48] = WordByte[ ( MatchPos * 5 ) + 3 ] ;
WordByte[49] = WordByte[ ( MatchPos * 5 ) + 4 ] ;
}
Channel = 0;
if ( WordByte[45] & ( 1 << 2 ) ) { Channel = 1; }
if ( WordByte[45] & ( 1 << 3 ) ) { Channel = Channel + 2; }
if ( WordByte[46] & ( 1 << 4 ) ) { ManuSend = 1; } else { ManuSend = 0; }
// binär analyse der temperatur
Temp = 0 ;
if ( WordByte[46] & ( 1 << 3 ) ) { Temp = Temp + 1 ; }
if ( WordByte[46] & ( 1 << 2 ) ) { Temp = Temp + 2 ; }
if ( WordByte[46] & ( 1 << 1 ) ) { Temp = Temp + 4 ; }
if ( WordByte[46] & ( 1 << 0 ) ) { Temp = Temp + 8 ; }
if ( WordByte[47] & ( 1 << 7 ) ) { Temp = Temp + 16 ; }
if ( WordByte[47] & ( 1 << 6 ) ) { Temp = Temp + 32 ; }
if ( WordByte[47] & ( 1 << 5 ) ) { Temp = Temp + 64 ; }
if ( WordByte[47] & ( 1 << 4 ) ) { Temp = Temp + 128 ; }
if ( WordByte[47] & ( 1 << 3 ) ) { Temp = Temp + 256 ; }
if ( WordByte[47] & ( 1 << 2 ) ) { Temp = Temp + 512 ; }
if ( WordByte[47] & ( 1 << 1 ) ) { Temp = Temp + 1024 ; }
if ( WordByte[47] & ( 1 << 0 ) ) { Temp = Temp * - 1 ; }
// binär analyse der luftfeuchtigkeit
Feuchte = 0 ;
if ( WordByte[48] & ( 1 << 7 ) ) { Feuchte = Feuchte + 1 ; }
if ( WordByte[48] & ( 1 << 6 ) ) { Feuchte = Feuchte + 2 ; }
if ( WordByte[48] & ( 1 << 5 ) ) { Feuchte = Feuchte + 4 ; }
if ( WordByte[48] & ( 1 << 4 ) ) { Feuchte = Feuchte + 8 ; }
if ( WordByte[48] & ( 1 << 3 ) ) { Feuchte = Feuchte + 10 ; }
if ( WordByte[48] & ( 1 << 2 ) ) { Feuchte = Feuchte + 20 ; }
if ( WordByte[48] & ( 1 << 1 ) ) { Feuchte = Feuchte + 40 ; }
if ( WordByte[48] & ( 1 << 0 ) ) { Feuchte = Feuchte + 80 ; }
TCP_SEND("%02d:%02d:%02d %s temp: Msg=%d i=%04x ch=%d t=%d h=%d m=%d M=%d/%d d=%02x%02x%02x%02x%02x\n",
CLOCK_HOUR(), CLOCK_MIN(), CLOCK_SEC(), CONF_HOSTNAME, MessageCnt, InterruptCall, Channel, Temp, Feuchte, ManuSend, MatchPos, MatchCount, WordByte[45], WordByte[46], WordByte[47], WordByte[48], WordByte[49]);
for (Count1 = 0; Count1 < 45; Count1 ++) { WordByte[Count1] = 0; }
}
if ( OWSend >= 1 ) {
OWSend = 0;
TCP_SEND("%02d:%02d:%02d %s OneWire: Sensor=%d: Fam=%02x ID=%02x%02x%02x%02x%02x%02x crc=%02x Temp=%d\n",
CLOCK_HOUR(), CLOCK_MIN(), CLOCK_SEC(), CONF_HOSTNAME,
(OWSendSensor + 1),
OW_RomAddrLst[((OWSendSensor * 8) )],
OW_RomAddrLst[((OWSendSensor * 8) + 1 )], OW_RomAddrLst[((OWSendSensor * 8) + 2 )],
OW_RomAddrLst[((OWSendSensor * 8) + 3 )], OW_RomAddrLst[((OWSendSensor * 8) )],
OW_RomAddrLst[((OWSendSensor * 8) + 5 )], OW_RomAddrLst[((OWSendSensor * 8) + 6 )],
OW_RomAddrLst[((OWSendSensor * 8) + 7 )],
OWTemp);
}
MarkSending ++;
if ( MarkSending >= 150 ) {
MarkCnt ++;
MarkSending = 0;
TCP_SEND("%02d:%02d:%02d %s MarkCnt=%d Sensors=%d\n",
CLOCK_HOUR(), CLOCK_MIN(), CLOCK_SEC(), CONF_HOSTNAME, MarkCnt, OW_SensorsFound)
}
PT_YIELD(pt);
}
TCP_HANDLER_END();
OWGetTimer ++;
if ( OWGetTimer >= 3000 ) {
if ( OWGetTimer == 3001 ) {
ow_temp_start_convert(NULL,0);
OWSendSensorOld=255;
}
if ( OWGetTimer >= 3250 ) {
OWSendSensor=(OWGetTimer - 3250) / 100;
if ( OWSendSensor >= OW_SensorsFound ) {
OWGetTimer = 0;
goto NoSend;
}
if ( OWSendSensor == OWSendSensorOld ) { goto NoSend; }
struct ow_rom_code_t ow_rom = {{ .bytewise = {
OW_RomAddrLst[((OWSendSensor * 8) )], OW_RomAddrLst[((OWSendSensor * 8) + 1 )],
OW_RomAddrLst[((OWSendSensor * 8) + 2 )], OW_RomAddrLst[((OWSendSensor * 8) + 3 )],
OW_RomAddrLst[((OWSendSensor * 8) + 4 )], OW_RomAddrLst[((OWSendSensor * 8) + 5 )],
OW_RomAddrLst[((OWSendSensor * 8) + 6 )], OW_RomAddrLst[((OWSendSensor * 8) + 7 )],
}}};
OWTemp = ow_temp(&ow_rom);
OWSend = 1;
OWSendSensorOld = OWSendSensor;
NoSend:
OWGetTimer=OWGetTimer;
}
}
if ( InterruptCall != InterruptCallOld ) {
SendMessage ++;
}
NetWatchDog ++;
if ( NetWatchDog == 15000 ) { // 5 Min
init_enc28j60();
} else {
if ( NetWatchDog == 30000 ) { // 10 Min
status.request_reset = 1;
}
}
dnl if ( reset == 1) {
dnl EndlessLoop:
dnl goto EndlessLoop;
dnl }
CONTROL_END
// ###############################################################################################################################
void CalcWordBytePos () {
WordBytePos=(( BitPos / 8 ) + ( WordCound * 5 ));
if ( WordBytePos >= 40 ) {
BitPos = 0 ;
WordCound = 0 ;
WordBytePos=(( BitPos / 8 ) + ( WordCound * 5 ));
}
}
ISR(INT0_vect) {
timer_0 = TCNT0;
InterruptCall ++;
CalcWordBytePos();
if ( timer_0 >= Time6 && timer_0 <= ( Time6 + 3 ) ) { // _ (puls)
} else if ( timer_0 == Time0 || timer_0 == ( Time0 + 1 ) ) { // L 0 (L - Bit - Normal)
WordByte[WordBytePos] &= ~(1 << ( 7 - (BitPos % 8))); // WordByte[n] = 0;
BitPos ++ ;
} else if ( timer_0 == Time1 || timer_0 == ( Time1 + 1 ) ) { // H 1 (H - Bit - Normal)
WordByte[WordBytePos] |= 1 << ( 7 - (BitPos % 8)); // WordByte[n] = 1;
BitPos ++ ;
} else if ( timer_0 == Time2 || timer_0 == ( Time2 + 1 ) ) { // End-l 2 (Ende Datenwort L)
WordByte[WordBytePos] &= ~(1 << ( 7 - (BitPos % 8))); // WordByte[n] = 0;
BitPos ++ ;
} else if ( timer_0 == Time3 || timer_0 == ( Time3 + 1 ) ) { // End-h 3 (Ende Datenwort H)
WordByte[WordBytePos] |= 1 << ( 7 - (BitPos % 8)); // WordByte[n] = 1;
BitPos ++ ;
} else if ( timer_0 >= Time4 && timer_0 <= ( Time4 + 5 ) ) { // Start 4 (Start Datenwort)
if ( BitPos != 1 ) {
WordCound ++ ;
CalcWordBytePos();
WordByte[ ( WordBytePos ) ] = 0 ;
WordByte[ ( WordBytePos + 1 ) ] = 0 ;
WordByte[ ( WordBytePos + 2 ) ] = 0 ;
WordByte[ ( WordBytePos + 3 ) ] = 0 ;
WordByte[ ( WordBytePos + 4 ) ] = 0 ;
}
BitPos = 0;
} else { // ? 8 (Nicht Erkanntes Timing)
BitPos ++ ;
}
TCNT0 = 0;
}
// ###############################################################################################################################
ISR(TIMER0_OVF_vect) {
if ( WordCound >= 3 ) {
// zeitüberschreitung, warscheinlich ende einer übertragung, ausgabe aktivieren
Analysis[8] |= (1 << 2);
}
BitPos = 0;
WordCound = 0;
}
// ###############################################################################################################################
dnl ecmd_feature(res, "res", res)
/*
-- Ethersex META --
ecmd_feature(nwd, "nwd", Network Watchdog)
ecmd_feature(m1on, "m1on", [value], Motor 1 on)
ecmd_feature(m1off, "m1off", Motor 1 off)
ecmd_feature(m2on, "m2on", [value], Motor 2 on)
ecmd_feature(m2off, "m2off", Motor 2 off)
ecmd_feature(charge, "charge", Charge Accu)
ecmd_feature(discharge, "discharge", Discarge Accu)
ecmd_feature(owrl, "owrl", ow refresh list)
*/