* Applications Measure of electrical energy using eCos with Driver ADC 16bits two Chanels using embeded WebServer and LwIp
@ 2007-03-15 21:19 Jhoberg
0 siblings, 0 replies; 2+ messages in thread
From: Jhoberg @ 2007-03-15 21:19 UTC (permalink / raw)
To: ecos-maintainers
[-- Attachment #1: Type: text/plain, Size: 431 bytes --]
This Develop capture two signals a electrical current and another one
of voltage,
with its respective adjustment of signal using operational-amplifer
and scaling to 0-5V the signals.
* Driver for ADC_7654 the 16 bits.
* Lwip
* WebServer embedded
* Control of load using a Triac.
Show the information the energy , values instant the current
electrical and voltage, Control of the load using the Web Server
embedded with a button.
[-- Attachment #2: WebEnergyCounter.c --]
[-- Type: text/x-csrc, Size: 15397 bytes --]
#Develp by Jhoberg Quevedo
#email: jrquevedor@gmail.com
#include "lwip/debug.h"
#include "lwip/stats.h"
#include "lwip/tcp.h"
#include <pkgconf/io_serial.h>
#include <pkgconf/libc.h>
#include <cyg/io/io.h>
#include <cyg/hal/hal_io.h>
#include <cyg/hal/hal_intr.h>
#include <cyg/io/serial.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <float.h>
#include <stdlib.h>
#include "html.h"
#define MAXSIZETCP 2048
// <img src=gnu.bmp ></img>
// <applet code=GraphApplet.class width=300 height=120>
// </applet>
volatile char * cadenainfo;
volatile char * apcomando;
char txdata [MAXSIZETCP];
char info[512];
//******************************************Definicion variables eCos
#define STACK_SIZE 0x800
cyg_alarm_t test_timer1;
static cyg_thread thread_s[1];
cyg_thread_entry_t samples_program;
cyg_handle_t samples_thread;
cyg_handle_t http_thread;
static char stack[1][STACK_SIZE];
cyg_mutex_t adcblock;
cyg_handle_t handle_ADC_ISR0;
cyg_interrupt intrADC;
cyg_sem_t semaforo_ADC,semaforo_nsamples;
cyg_io_handle_t handle_IO;
int aux=0;
//******************************************fin Definicion variables eCos
#define LED_1 (1<<16)
#define LED_2 (1<<17)
#define LED_3 (1<<18)
int axgnd=0;
int axgndi=0;
int axmin=0;
int axmax=0;
double *toppw;
double axenergy;
float jh=0;
float jh2=0;
float jh3=0;
float axjh=0;
//******************************************definicion de la clase ADC ANALOG DEVICE en C
#define NSAMPLES 64
#define FAMPLES 3840
static cyg_uint32 ADRMEMORYK=0x297;
int Mapear(void);
bool ConvertionStart(void);
bool ReadAdc(unsigned short *,cyg_uint32 *);
bool setChanel(int);
bool fpcalc(float axntfpv,float axntfpi);
int Mapear(void)
{
HAL_WRITE_UINT32(AT91_PIO + AT91_PIO_PDR ,AT91_PIO_PSR_NCS3);
HAL_WRITE_UINT32(AT91_EBI + AT91_EBI_MCR,AT91_EBI_MCR_ALE_8M|(0<<4) );
HAL_WRITE_UINT32(AT91_EBI + AT91_EBI_CSR1, AT91_EBI_CSR_DBW_16 | AT91_EBI_CSR_NWS_8 | AT91_EBI_CSR_WSE
| AT91_EBI_CSR_PAGES_1M | AT91_EBI_CSR_TDF_1 | AT91_EBI_CSR_CSEN | (ADRMEMORYK<<20) );
HAL_WRITE_UINT32(AT91_PIO + AT91_PIO_PER , AT91_PIO_PSR_TIOA1|AT91_PIO_PSR_P23|AT91_PIO_PSR_P24 );
HAL_WRITE_UINT32(AT91_PIO + AT91_PIO_OER, AT91_PIO_PSR_TIOA1|AT91_PIO_PSR_P23|AT91_PIO_PSR_P24 );
HAL_WRITE_UINT32(AT91_PIO + AT91_PIO_SODR, AT91_PIO_PSR_TIOA1);
//definicion de los pines para escojer un canal de conversion defaul ch3
HAL_WRITE_UINT32(AT91_PIO + AT91_PIO_SODR, AT91_PIO_PSR_P23);
HAL_WRITE_UINT32(AT91_PIO + AT91_PIO_CODR, AT91_PIO_PSR_P24);
return 1;
//0,0 -> B
//1,0 -> Voltaje
}
bool ConvertionStart(void)
{
HAL_WRITE_UINT32(AT91_PIO + AT91_PIO_CODR, AT91_PIO_PSR_TIOA1);
HAL_WRITE_UINT32(AT91_PIO + AT91_PIO_SODR, AT91_PIO_PSR_TIOA1);
return 1;
}
bool ReadAdc(cyg_uint16 *Datoax, cyg_uint32 *address)
{
HAL_READ_UINT16((*address<<20) ,*Datoax );
return 1;
}
bool setChanel(int chop)
{
switch(chop)
{
case 0:{ HAL_WRITE_UINT32(AT91_PIO + AT91_PIO_CODR, AT91_PIO_PSR_P23);
HAL_WRITE_UINT32(AT91_PIO + AT91_PIO_CODR, AT91_PIO_PSR_P24);
//voltaje
break;}
case 1:{ HAL_WRITE_UINT32(AT91_PIO + AT91_PIO_CODR, AT91_PIO_PSR_P23);
HAL_WRITE_UINT32(AT91_PIO + AT91_PIO_SODR, AT91_PIO_PSR_P24);
break;}
case 2:{ HAL_WRITE_UINT32(AT91_PIO + AT91_PIO_SODR, AT91_PIO_PSR_P23);
HAL_WRITE_UINT32(AT91_PIO + AT91_PIO_CODR, AT91_PIO_PSR_P24);
//corriente
break;}
case 3:{ HAL_WRITE_UINT32(AT91_PIO + AT91_PIO_SODR, AT91_PIO_PSR_P23);
HAL_WRITE_UINT32(AT91_PIO + AT91_PIO_SODR, AT91_PIO_PSR_P24);
break;}
}
return 1;
}
typedef struct adc_7654
{
volatile cyg_uint32 ADRMEMORY;
volatile cyg_uint16 Dato;
volatile int samplesH[NSAMPLES];
int *i;
volatile int *Samples[NSAMPLES];
char *cDato;
char *cSamples;
int topsamples;
int numsample;
unsigned long energysample;
float energytop;
double *EnergyDato;
int rateenergy;
int MaxV;
int MinV;
volatile unsigned int *MaxI;
volatile unsigned int *MinI;
volatile float instantVol;
volatile float instantCur;
int Comdato;
int ntfpv;
int ntfpi;
float fp;
int Comando;
int promedioH;
int promedioV;
int (* apMapear)(void);
bool (* apConvertionStart)(void);
bool (* apReadAdc)(cyg_uint16 *,cyg_uint32 *);
bool (* apsetChanel)(int);
};
volatile struct adc_7654 adc,axadc;
bool fpcalc(float axntfpv,float axntfpi)
{
if(axntfpv>axntfpi) {
axntfpv=axntfpv-axntfpi;
axntfpi=axntfpv*9817477E-8;
axntfpv=cos(axntfpi);
axadc.fp=axntfpv;
}
else{
axntfpv=axntfpi-axntfpv;
axntfpi=axntfpv*9817477E-8;
axntfpv=cos(axntfpi);
axadc.fp=axntfpv;
}
return 1;
}
cyg_uint32 fun_isr0 (cyg_vector_t vector, cyg_addrword_t data )
{
cyg_uint32 resultPIO;
// HAL_WRITE_UINT32( AT91_PIO + AT91_PIO_SODR ,LED_2 );
HAL_READ_UINT32(AT91_PIO + AT91_PIO_ISR, resultPIO);
cyg_interrupt_mask(vector);
cyg_interrupt_acknowledge(vector);
return (CYG_ISR_HANDLED | CYG_ISR_CALL_DSR);
}
void fun_dsr0(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data){
cyg_interrupt_unmask(vector);
}
// funcion de muestreo, con frecuencia variable
void bitAdc_alarm_func(cyg_handle_t alarmH, cyg_addrword_t data)
{
axmax=0;
axmin=0;
axgnd=0;
jh=0;
jh2=0;
jh3=0;
HAL_WRITE_UINT32( AT91_PIO + AT91_PIO_SODR ,LED_2 );
cyg_scheduler_lock();
cyg_mutex_lock(&adcblock);
if(adc.numsample>NSAMPLES)
{
HAL_WRITE_UINT32( AT91_PIO + AT91_PIO_CODR ,LED_2 );
if(axadc.Comando==0)
axadc=(struct adc_7654)adc;
cyg_semaphore_post(&semaforo_nsamples);
if((*adc.EnergyDato)<adc.energytop)
{
axmax=adc.MaxV;
axmin=adc.MinV;
//axgnd=adc.promedioV;
// axgnd=(int)(axgnd/NSAMPLES);
axgnd=axmin+(((axmax)-(axmin))/2 );
jh2=(float)(axmax-axgnd);
jh=1E3/65535E0;
if(jh2>0)
adc.instantVol=jh2*jh;
else
adc.instantVol=120E0;
axmax=(*adc.MaxI);
axmin=(*adc.MinI);
// axgnd=adc.promedioH;
//axgnd=(int)(axgnd/NSAMPLES);
axgnd=axmin+(((axmax)-(axmin))/2 );
//para calcuar con dcgnd variable
jh2=(float)( axmax - axgnd);
jh=4565E-6;
if( jh2 > 0)
jh3=jh2*jh;
else
jh3=0;
if(jh3>0)
{
adc.instantCur=jh3;
jh=(adc.instantVol*(jh3));
*toppw+=jh;
*adc.EnergyDato=*toppw;
}
else{
adc.instantCur=0E0;
adc.instantVol=120E0;
}
adc.MaxV=0;
*adc.MinI=0xFFFF;
*adc.MaxI=0;
adc.MinV=0xFFFF;
adc.numsample=0;
adc.promedioH=0;
adc.promedioV=0;
adc.energysample++;
}else{
adc.Comdato=1;
}
}else
{
//dato de B
adc.apsetChanel(2);
adc.apConvertionStart();
adc.apReadAdc(&adc.Dato,adc.ADRMEMORY);
adc.samplesH[adc.numsample]=adc.Dato;
adc.promedioH+=adc.samplesH[adc.numsample];
if(*adc.MaxI<adc.samplesH[adc.numsample])
{
*adc.MaxI=adc.samplesH[adc.numsample];
adc.ntfpi=adc.numsample;
}
if(*adc.MinI>adc.samplesH[adc.numsample])
*adc.MinI=(unsigned int)adc.samplesH[adc.numsample];
adc.i=7;
//dato de V
adc.apsetChanel(0);
adc.apConvertionStart();
adc.apReadAdc(&adc.Dato,adc.ADRMEMORY);
adc.Samples[adc.numsample]=adc.Dato;
adc.promedioV+=(*adc.Samples[adc.numsample]);
if(adc.MaxV<adc.Samples[adc.numsample])
{
adc.MaxV=adc.Samples[adc.numsample];
adc.ntfpi=adc.numsample;
}
if(adc.MinV>adc.Samples[adc.numsample])
{
adc.MinV=adc.Samples[adc.numsample];
adc.ntfpv=adc.numsample;
}
adc.numsample++;
}
cyg_mutex_unlock(&adcblock);
cyg_scheduler_unlock();
}
void samples_program(cyg_addrword_t data)
{
int message = (int) data;
int delay;
int n,x;
char output;
cyg_handle_t test_counterH, system_clockH, test_alarmH;
cyg_tick_count_t ticks;
cyg_alarm test_alarm;
cyg_handle_t tiempo_real;
unsigned how_many_alarms = 0, prev_alarms = 0, tmp_how_many;
system_clockH = cyg_real_time_clock();
cyg_clock_to_counter(system_clockH, &test_counterH);
cyg_alarm_create(test_counterH, bitAdc_alarm_func,
(cyg_addrword_t) &how_many_alarms ,
&test_alarmH, &test_alarm);
cyg_alarm_initialize(test_alarmH, cyg_current_time()+1, 2);
for (;;)
{
}
}
// fin frecuencia de muestreo
//******************************************fin de la clase ADC de ANALOG DEVICE en C
struct http_state {
char *file;
u32_t left;
u8_t retries;
};
//-----------------------------------------------------------------------------------
static void conn_err(void *arg, err_t err)
{
struct http_state *hs;
hs = arg;
mem_free(hs);
}
//-----------------------------------------------------------------------------------
static void close_conn(struct tcp_pcb *pcb, struct http_state *hs)
{
tcp_arg(pcb, NULL);
tcp_sent(pcb, NULL);
tcp_recv(pcb, NULL);
mem_free(hs);
tcp_close(pcb);
}
//-----------------------------------------------------------------------------------
static void send_data(struct tcp_pcb *pcb, struct http_state *hs)
{
err_t err;
u16_t len;
// We cannot send more data than space available in the send
// buffer.
if(tcp_sndbuf(pcb) < hs->left) {
len = tcp_sndbuf(pcb);
} else {
len = hs->left;
}
do {
err = tcp_write(pcb, hs->file, len, 0);
if(err == ERR_MEM) {
len /= 2;
}
} while(err == ERR_MEM && len > 1);
if(err == ERR_OK) {
hs->file += len;
hs->left -= len;
}
}
//-----------------------------------------------------------------------------------
static err_t http_poll(void *arg, struct tcp_pcb *pcb)
{
struct http_state *hs;
hs = arg;
// printf("Polll\n");
if(hs == NULL) {
// printf("Null, close\n");
tcp_abort(pcb);
return ERR_ABRT;
} else {
++hs->retries;
if(hs->retries == 4) {
tcp_abort(pcb);
return ERR_ABRT;
}
send_data(pcb, hs);
}
return ERR_OK;
}
//-----------------------------------------------------------------------------------
static err_t http_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
{
struct http_state *hs;
hs = arg;
hs->retries = 0;
if(hs->left > 0) {
send_data(pcb, hs);
} else {
close_conn(pcb, hs);
}
return ERR_OK;
}
//
int reqtypehttp(char *data)
{
int i,aux,j;
char tipo[20];
int vartop,varmem,var;
if(strncmp("GET / ",data,6))
{
aux=0;
while( data[aux+5] != NULL )
{
if( ((char *)data+5)[aux]==' ' )
break;
else
tipo[aux]=((char *)data+5)[aux];
aux++;
}
}else
{
cyg_semaphore_wait(&semaforo_nsamples);
axadc.Comando=1;
j=sizeof(vpage_index);
memcpy(txdata,vpage_index,j);
j=strlen(txdata);
fpcalc(axadc.ntfpv,axadc.ntfpi);
// axenergy=(double)*axadc.EnergyDato;
//axenergy=axenergy/21E6;
sprintf(info,"<H3>Datos remotos</H3><P><table align=\"left\" border=\"3\" valign=\"bottom\" bgcolor=\"lightGray\"><TR><TD>Energia</TD><TD>%f_Kw*h</TD></TR><TR><TD>Vinsta:</TD><TD>%f_V</TR></TD><TR><TD>Iinsta:</TD><TD>%f_A</TD></TR><TR><TD>F.P</TD><TD>%f</TD></TR><TR><TD>Ciclos</TD><TD>%lu</TD></TR><TR><FORM name=\"comando\" method=\"POST\"><TD><INPUT type=\"button\" value=\"Conectar\"></TD><TD><INPUT type=\"submit\" value=\"Apagar\"></TD></FORM></TR></TABLE></P>",*axadc.EnergyDato,axadc.instantCur,axadc.instantVol,axadc.fp,axadc.energysample);
j=strlen(txdata);
i=strlen(info);
memcpy(&(txdata[j]),info,i);
/*
vartop=0;
for(var=0;var<NSAMPLES;var++)
{
sprintf(apcomando,"%d, \n",axadc.Samples[var]);
varmem=0;
while((varmem<6)&&((char *)apcomando)[varmem]!=' ')
{
((char *)cadenainfo+vartop)[varmem]=(char *)apcomando[varmem];
varmem++;
}
vartop=vartop+varmem;
}
j=strlen(txdata);
i=sizeof(vpage_cierre);
memcpy(&(txdata[j]),vpage_cierre,i);
j=strlen(txdata);
i=sizeof(cierre);
memcpy(&(txdata[j]),cierre,i);
*/
axadc.Comando=0;
return sizeof(txdata);
}
return 1;
}
//
//-----------------------------------------------------------------------------------
static err_t http_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
{
int i;
char *data;
struct http_state *hs;
hs = arg;
int sizetx;
if(err == ERR_OK && p != NULL) {
// Inform TCP that we have taken the data.
tcp_recved(pcb, p->tot_len);
if(hs->file == NULL) {
data = p->payload;
sizetx=reqtypehttp(data);
hs->file = &txdata;
hs->left = sizetx;
pbuf_free(p);
send_data(pcb, hs);
tcp_sent(pcb, http_sent);
} else {
pbuf_free(p);
close_conn(pcb, hs);
}
} else {
pbuf_free(p);
}
if(err == ERR_OK && p == NULL) {
close_conn(pcb, hs);
}
return ERR_OK;
}
//-----------------------------------------------------------------------------------
static err_t http_accept(void *arg, struct tcp_pcb *pcb, err_t err)
{
struct http_state *hs;
tcp_setprio(pcb, TCP_PRIO_MIN);
// Allocate memory for the structure that holds the state of the
// connection.
hs = mem_malloc(sizeof(struct http_state));
if(hs == NULL) {
return ERR_MEM;
}
// Initialize the structure.
hs->file = NULL;
hs->left = 0;
hs->retries = 0;
// Tell TCP that this is the structure we wish to be passed for our
// callbacks.
tcp_arg(pcb, hs);
// Tell TCP that we wish to be informed of incoming data by a call
// to the http_recv() function.
tcp_recv(pcb, http_recv);
tcp_err(pcb, conn_err);
tcp_poll(pcb, http_poll, 4);
return ERR_OK;
}
//-----------------------------------------------------------------------------------
void httpd_init(void *arg)
{
struct tcp_pcb *pcb;
pcb = tcp_new();
tcp_bind(pcb, IP_ADDR_ANY, 80);
pcb = tcp_listen(pcb);
tcp_accept(pcb, http_accept);
while(1)
cyg_thread_delay(1000);
}
// funcin principal
void webserver(cyg_addrword_t p)
{
lwip_init();
sys_thread_new(httpd_init, (void*)"httpd",7);
}
void cyg_user_start(void)
{
//inicializacio de la clase
adc.topsamples=NSAMPLES;
adc.numsample=0;
adc.energysample=0;
adc.energytop=216E6; //1KW/h
adc.cDato=(char *)malloc(sizeof(char [4]));
adc.apMapear=Mapear;
adc.apConvertionStart=ConvertionStart;
adc.apReadAdc=ReadAdc;
adc.apsetChanel=setChanel;
adc.ADRMEMORY=&ADRMEMORYK;
adc.apMapear();
adc.Comdato=0;
adc.EnergyDato=malloc(sizeof(double *));;
adc.rateenergy=10;
adc.MaxI=malloc(sizeof(int *));
adc.MinI=malloc(sizeof(int *));
*adc.MaxI=0;
*adc.MinI=0xFFFF;
adc.instantVol=0;
adc.instantCur=0;
adc.ntfpi=0;
adc.ntfpv=0;
adc.fp=1;
adc.Comando=0;
adc.promedioH=0;
adc.promedioV=0;
toppw=malloc(sizeof(float *));
*toppw=0;
apcomando=malloc(sizeof(char *[8]));
cadenainfo=malloc(sizeof(char *[100]));
//fin de inicializacion
cyg_mutex_init(&adcblock);
cyg_semaphore_init(&semaforo_nsamples,0);
cyg_semaphore_init(&semaforo_ADC,0);
cyg_thread_create(10, samples_program, (cyg_addrword_t) 0,
"Hilo para AD7654 de ANALOG", (void *) stack[0],STACK_SIZE,
&samples_thread,&thread_s[0]);
cyg_thread_create(10,webserver,0,
"thread", &stack[1],STACK_SIZE,
&http_thread,&thread_s[1]);
cyg_thread_resume(http_thread);
cyg_thread_resume(samples_thread);
}
[-- Attachment #3: html.h --]
[-- Type: text/x-chdr, Size: 2238 bytes --]
/*
char vpage_index[]="<HTML><HEAD> <title>Contador de Energia Electrica v1.1 </title> </HEAD> <BODY>
<SCRIPT>document.write(\"<H3>Tipo de navegador : [\"+ navigator.appName +\", Version\"+navigator.appVersion+\"]</H3>\")</SCRIPT> <H1>Datos de Consumo de Energia</H1><table align=\"left\"> <TD align=\"left\"><FORM onSubmit=\"return false\" name=\"formprueba\"> <tr><td>Entre nombre : </td> <td><INPUT TYPE=\"text\" NAME=\"nombre\" SIZE= 10></td></tr><tr><td>Equitos</td><td><SELECT name=\"equipos\"><OPTION value=\"1\">192.168.2.1</OPTION> <OPTION value=\"2\" >192.168.2.2</OPTION></td></tr> <tr> <td>Modulo 1</td> <td><INPUT type=\"checkbox\" name=\"opcion1\" align=\"top\"></td></tr><TR> <TD>Registrar</TD><TD><INPUT TYPE=\"button\" value=\"Register\" onClick=\"register(document.formprueba.nombre.value)\"></TD></TR>
</FORM> <TR><TD><H3 align=\"left\">Links</H3></TD> <TD><A href=\"mailto:destecno@cir.org.co?subject=informacion\" title=\"prueba\" href=\"Informacion: \">destecno@cif.org.co</A></TD></TR></table>
";
*/
char vpage_index[]="<HTML><HEAD> <title>Contador de Energia Electrica v1.1 </title> </HEAD> <BODY>
<SCRIPT>document.write(\"<H5>Tipo de navegador : [\"+ navigator.appName +\", Version\"+navigator.appVersion+\"]</H5>\")</SCRIPT><H1>Contador de Energia remoto</H1><TD><A href=\"mailto:destecno@cir.org.co?subject=informacion\" title=\"prueba\" href=\"Informacion: \">destecno@cif.org.co</A></TD><P>Carga de prueba configurada a 1Kw*h, una vez se a utilizado la energia el equipo apaga la carga remota</P></BODY></HTML>";
char vpage_form[]="
<table align=\"left\"> <TD align=\"left\"><FORM onSubmit=\"return false\" name=\"formprueba\"> <tr><td>Entre nombre : </td> <td><INPUT TYPE=\"text\" NAME=\"nombre\" SIZE= 10></td></tr><tr><td>Equitos</td><td><SELECT name=\"equipos\"><OPTION value=\"1\">192.168.2.1</OPTION> <OPTION value=\"2\" >192.168.2.2</OPTION></td></tr> <tr> <td>Modulo 1</td> <td><INPUT type=\"checkbox\" name=\"opcion1\" align=\"top\"></td></tr><TR> <TD>Registrar</TD><TD><INPUT TYPE=\"button\" value=\"Register\" onClick=\"register(document.formprueba.nombre.value)\"></TD></TR>
</FORM> <TR><TD><H3 align=\"left\">Links</H3></TD></TR></table>
";
char vpage_cierre[]=
"</BODY></HTML>";
^ permalink raw reply [flat|nested] 2+ messages in thread
* Applications Measure of electrical energy using eCos with Driver ADC 16bits two Chanels using embeded WebServer and LwIp
@ 2007-03-15 21:04 Jhoberg
0 siblings, 0 replies; 2+ messages in thread
From: Jhoberg @ 2007-03-15 21:04 UTC (permalink / raw)
To: ecos-maintainers
Development that it captures by means of driver for ADC of two
channels a signal of electrical current another one of voltage, Driver
for ADC_7654, calculate the value of the consumed electrical energy
and drive a load connected to triac, this
information is visualized by Web server that show values the current
and voltaje,
and power factor and control of the load.
* Driver ADC two Chanesl
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2007-03-15 21:19 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2007-03-15 21:19 Applications Measure of electrical energy using eCos with Driver ADC 16bits two Chanels using embeded WebServer and LwIp Jhoberg
-- strict thread matches above, loose matches on Subject: below --
2007-03-15 21:04 Jhoberg
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).