Here's the device in action:

Assembly of the gadget is fairly easy, but the project must have an external power supply AND a battery. It also only works properly on the Arduino Mega2560 (other options were tried).

Parts Required:

  1. Emic2 text to speech module
  2. Arduino Mega 2560
  3. Adafruit SIM800L module
  4. RtcDS3231 module
  5. 5v power supply
  6. 1000 mAH LiPo battery
  7. Speaker
  8. GPRS aerial
  9. Ufl to SMA aerial connector

Technical Connections:

  1. Mega SCL to DS3231 SCL
  2. Mega SDA to DS3231 SDA
  3. Mega D12 to Emic2 Sin
  4. Mega D11 to Emic2 Sout
  5. Mega D10 to Fona Tx
  6. Mega D9 to Fona Rx
  7. Fona Key to ground
  8. Fona 5v (in the middle of the board) to 5v
  9. Fona Vio to 5v

Other than the above, it's all pretty obvious. The Emic 2 has SP- and SP+ for the speaker. Connect all the modules to 5v and ground as indicated on the modules themselves.

So, with the hardware rigged up, it's just a matter of uploading code to the Arduino and creating a PHP file to read the weather station database.

The really great thing about this project is that you don't actually need your own weather station or PHP server as you can just read mine - no usernames or passwords required! Just set up the hardware as above and upload the following code to the Arduino Mega:

#define rxPin   11  // Serial input (connects to Emic 2's SOUT pin)
#define txPin   12  // Serial output (connects to Emic 2's SIN pin)
#define ledPin  13  // Most Arduino boards have an on-board LED on this pin
#include "Adafruit_FONA.h"
#define FONA_RX 9
#define FONA_TX 10
#define FONA_RST 4
#include <SoftwareSerial.h>
// CONNECTIONS:
// DS3231 SDA --> SDA
// DS3231 SCL --> SCL
// DS3231 VCC --> 3.3v or 5v
// DS3231 GND --> GND

#if defined(ESP8266)
#include <pgmspace.h>
#else
#include <avr/pgmspace.h>
#endif
    int i;
    int hobbits;
    int orks;
#include <Wire.h> // must be included here so that Arduino library object file references work
#include <RtcDS3231.h>


RtcDS3231<TwoWire> Rtc(Wire);
char datestringB[20];

// this is a large buffer for replies
char replybuffer[255];
char url[80];

#include <SoftwareSerial.h>
SoftwareSerial fonaSS = SoftwareSerial(FONA_TX, FONA_RX);
SoftwareSerial *fonaSerial = &fonaSS;
SoftwareSerial emicSerial =  SoftwareSerial(rxPin, txPin);

Adafruit_FONA fona = Adafruit_FONA(FONA_RST);

uint8_t readline(char *buff, uint8_t maxbuff, uint16_t timeout = 0);

uint8_t type;
int switchStatus=0;
int smsnumB=1000;
String textMessage;
String textMessageShort;
int previousNum =0;
int smsNumberStatus=0;
int z=0;
int p=0;
int selectSMS=0;
int speakStatus=0;
String d;
String nothing;
char webpage[1000];
int g=0;

  
void setup() 
{   Serial.begin(9600);
    Serial.print("compiled: ");
    Serial.print(__DATE__);
    Serial.println(__TIME__);
    //--------RTC SETUP ------------
    Rtc.Begin();
    RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__);
    printDateTime(compiled);
    Serial.println();
    RtcDateTime now = Rtc.GetDateTime();
    
   // while (!Serial);
    Serial.println("Hello There!");
    pinMode(LED_BUILTIN, OUTPUT);
    pinMode(ledPin, OUTPUT);
    pinMode(rxPin, INPUT);
    pinMode(txPin, OUTPUT);
    emicSerial.begin(9600);
    emicSerial.print('\n');             // Send a CR in case the system is already up
    while (emicSerial.read() != ':');   // When the Emic 2 has initialized and is ready, it will send a single ':' character, so wait here until we receive it
    delay(10);                          // Short delay
    emicSerial.flush();                 // Flush the receive buffer

   pinMode(4, INPUT_PULLUP); // Use this to reset Fona.
   digitalWrite(4, HIGH);
   delay(100);
   digitalWrite(4, LOW);

  Serial.println(F("FONA basic test"));
  Serial.println(F("Initializing....(May take 3 seconds)"));
    
  fonaSerial->begin(4800);
  if (! fona.begin(*fonaSerial)) {
    Serial.println(F("Couldn't find FONA"));
    //emic.speak("Bad news .. We could not find the Fona .. Please reset all devices");
    while (1);
  }
     emicSerial.println("P1");
     emicSerial.println("V12"); // 18 is max.
     emicSerial.print('S');
     emicSerial.print(("Good news .. All systems are fully operational."));
     emicSerial.print('\n');
  //emic.speak("Good news .. we successfully connected with the Fona module");  
  type = fona.type();
  Serial.println(F("FONA is OK"));
  Serial.print(F("Found "));
  switch (type) {
    case FONA800L:
      Serial.println(F("FONA 800L")); break;
    case FONA800H:
      Serial.println(F("FONA 800H")); break;
    case FONA808_V1:
      Serial.println(F("FONA 808 (v1)")); break;
    case FONA808_V2:
      Serial.println(F("FONA 808 (v2)")); break;
    case FONA3G_A:
      Serial.println(F("FONA 3G (American)")); break;
    case FONA3G_E:
      Serial.println(F("FONA 3G (European)")); break;
    default: 
      Serial.println(F("???")); break;
  }
  
  // Print module IMEI number.
  char imei[15] = {0}; // MUST use a 16 character buffer for IMEI!
  uint8_t imeiLen = fona.getIMEI(imei);
  if (imeiLen > 0) {
    //Serial.print("Module IMEI: "); Serial.println(imei);
  }
  fona.setGPRSNetworkSettings(F("pp.vodafone.co.uk"), F("wap"), F("wap"));   // Change these settings! (Network APN, username ,password)
  delay(10000);
  void turnOffGPRS();
}


void loop() 
{   
    RtcDateTime now = Rtc.GetDateTime();
    //printDateTime(now);
    printMinute(now);
    printHour(now);
    //Serial.println();

    delay(1000);
        if (((hobbits==0)||(g==0))&&(orks>7)&&(orks!=0))
    {
     Serial.println("Hobbits are GO!");
     //if (hobbits==0){g=1;} //Makes it do it once only.
     // else {g=0;}
     g=1;
/////////////////////////////////////////////////////////////////////////////////////////////////////
    emicSerial.println("P0");
    //emicSerial.println("S [:phone arpa speak on][:rate 190][:n0][ GOW<400,12>DD<200,15>B<200,10>LLEH<200,19>EH<500,22>S<200,18>AH<100,18>MEH<200,16>K<100,13>AH<200,12>][:n0]");
     //emicSerial.print('S');
   //emicSerial.println("S [:phone arpa speak on][:rate 190][:n3][:dv as 100 f4 3300 sm 5 nf 50 bf 20 ri 100][AH<100,16> TAY<100> N<100> SH<100> N<100>_<100> ow<200> ll<300>_hx<100>yu<200>muw<100>n<300> s<200>____DHAX<300,16> weh<200>DHAX<200>_ah<200> p<200> dey<200> t<200> ih<200>z<100> nuh<200> ih<100>m<100>ih<100>n<100>ah<100>n<100>t<100>][:n0]");
     //emicSerial.print('\n');

    emicSerial.println("S [:phone arpa speak on][:rate 190][:n0][D<200,25>ix<200>nx<300>_ D<200,12>ax<200>nx<300>_d<200,24>ao<600>nx<400>_ix<200,15>t<100>z<200> t<100,16>ay<100,12>ay<100,18>ay<100,20>ay<100,22>m<200> t<100>uw<200> ch<100,20>eh<100>k<100> dhax<200,14> weh<200,16>dhax<200>r<100,15>][:n0]");
    emicSerial.print('\n'); 
    delay(8000);
    emicSerial.println("P1");
    emicSerial.print('S');
    emicSerial.print(("It's naaoww time to check the weather once more."));
    emicSerial.print('\n');
    flushSerial();
    networkStatus();
    turnOffGPRS();
    delay(10000);
    //emic.speak("now attempting to connect to g p r s once more");
    if (fona.enableGPRS(true))

      readWebpageA(); 
      delay(1000); 

      turnOffGPRS();
      for (int i =0 ; i < 60 ; i++) 
      {
      delay(1000); 
      //ledFlash(); 
      //delay(3600000);
      }     
     ;
////////////////////////////////////////////////////////////////////////////////////////////////////   
      emicSerial.println("P0");
      emicSerial.println("S [:phone arpa speak on][:rate 190][:n0][ GAA<400,12>DD<200,15>B<200,10>LLEH<200,19>EH<500,22>S<200,18>AH<100,18>MEH<200,16>K<100,13>AH<200,12>][:n0]");
      emicSerial.print('\n');   
    }
  

}
#define countof(a) (sizeof(a) / sizeof(a[0]))
void ledFlash()
{
      for (int h =0 ; h < 5 ; h++) 
      {
      digitalWrite(LED_BUILTIN, HIGH);   
      delay(20);                       
      digitalWrite(LED_BUILTIN, LOW);    
      delay(20); 
      }
}
void readWebpageA()
{
          // read website URL
        uint16_t statuscode;
        int16_t length;

    char url[80] = "http://www.goatindustries.co.uk/weather2/speech10.php";
   
        if (!fona.HTTP_GET_start(url, &statuscode, (uint16_t *)&length)) {
          Serial.println("Failed!");
        }
        int aa=0;
        while (length > 0) 
        {
          while (fona.available()) 
          {
            char c = fona.read();
            webpage[aa] = c;
            aa++;      
           // d = "Z" + c;    
            // Serial.write is too slow, we'll write directly to Serial register!
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
            loop_until_bit_is_set(UCSR0A, UDRE0); /* Wait until data register empty. */
            UDR0 = c;
#else
            Serial.write(c);
#endif
            length--;
            if (! length);
          }
        }
        Serial.println(F("\n****"));
        fona.HTTP_GET_end();

 // Serial.print("This should be character webpage: ....... "); Serial.println(webpage);
//  Serial.print("The value of P is: ....... "); Serial.println(p);
 // emic.speak(webpage); 
     emicSerial.println("P1");
     emicSerial.print('S');
     emicSerial.print(webpage);
     emicSerial.print('\n');


}
void turnOffGPRS()
{
          // Turn off GPRS:
        if (!fona.enableGPRS(false))
        Serial.println(F("No - Failed to turn off"));
        Serial.println("If the line above says OK, then GPRS has just been turned off");
        delay (1000);
}
void turnOnGPRS()
{
        delay (10000);
        Serial.println("Now attempting to turn on GPRS .........");
     //   if (!fona.enableGPRS(true))
     //   Serial.println(F("No - Failed to turn on"));
     //   Serial.println("GPRS is on if the line above shows 'OK'");
     //   Serial.println("Wait for 10 seconds to make sure GPRS is on ...........");        
     //   delay (10000);
        if (fona.enableGPRS(true));
        //ledFastFlashB();                          // Indicates a connect to GPRS.

        //Serial.println(("No - Failed to turn on"));
}

void networkStatus()
{
          // read the network/cellular status
        uint8_t n = fona.getNetworkStatus();
        Serial.print(F("Network status "));
        Serial.print(n);
        Serial.print(F(": "));
        if (n == 0) Serial.println(F("Not registered"));
        if (n == 1) Serial.println(F("Registered (home)"));
        if (n == 2) Serial.println(F("Not registered (searching)"));
        if (n == 3) Serial.println(F("Denied"));
        if (n == 4) Serial.println(F("Unknown"));
        if (n == 5) Serial.println(F("Registered roaming"));
}

void flushSerial()
{
  while (Serial.available())
    Serial.read();
}

char readBlocking() 
{
  while (!Serial.available());
  return Serial.read();
}
uint16_t readnumber() 
{
  uint16_t x = 0;
  char c;
  while (! isdigit(c = readBlocking())) {
    //Serial.print(c);
  }
  Serial.print(c);
  x = c - '0';
  while (isdigit(c = readBlocking())) {
    Serial.print(c);
    x *= 10;
    x += c - '0';
  }
  return x;
}

uint8_t readline(char *buff, uint8_t maxbuff, uint16_t timeout) 
{
  uint16_t buffidx = 0;
  boolean timeoutvalid = true;
  if (timeout == 0) timeoutvalid = false;

  while (true) {
    if (buffidx > maxbuff) {
      //Serial.println(F("SPACE"));
      break;
    }

    while (Serial.available()) {
      char c =  Serial.read();

      //Serial.print(c, HEX); Serial.print("#"); Serial.println(c);

      if (c == '\r') continue;
      if (c == 0xA) {
        if (buffidx == 0)   // the first 0x0A is ignored
          continue;

        timeout = 0;         // the second 0x0A is the end of the line
        timeoutvalid = true;
        break;
      }
      buff[buffidx] = c;
      buffidx++;
    }

    if (timeoutvalid && timeout == 0) {
      //Serial.println(F("TIMEOUT"));
      break;
    }
    delay(1);
  }
  buff[buffidx] = 0;  // null term
  return buffidx;
}
void printDateTime(const RtcDateTime& dt)
{
    char datestringA[20];

    snprintf_P(datestringA, 
            countof(datestringA),
            PSTR("%02u/%02u/%04u %02u:%02u"),
            dt.Month(),
            dt.Day(),
            dt.Year(),
            dt.Hour(),
            dt.Minute()
        //    dt.Second()
            );
    Serial.println(datestringA);

    
}
void printHour(const RtcDateTime& dt)
{
    char datestringB[20];

    snprintf_P(datestringB, 
            countof(datestringB),
            PSTR("%02u"),
            dt.Hour()
            );
   // Serial.println(datestringB);
        String rabbits;
    rabbits += datestringB;
    //Serial.print("datestringB ..... ");         Serial.println(datestringB);
    orks = rabbits.toInt();
    Serial.print("orks ..... "); Serial.println(orks);
}
void printMinute(const RtcDateTime& dt)
{
    char datestringB[20];

    snprintf_P(datestringB, 
            countof(datestringB),
            PSTR("%02u"),
            dt.Minute()
            );
   // Serial.println(datestringB);
        String rabbits;
    rabbits += datestringB;
    //Serial.print("datestringB ..... ");         Serial.println(datestringB);
    hobbits = rabbits.toInt();
    Serial.print("hobbits ..... "); Serial.println(hobbits);
}

Don't forget that there are a few Arduino libraries to download and install eg. Adafruit Fona etc. The above code will make your Arduino sing!


If you really must write your own PHP file, then here's how to do it. Be aware that the file needs to be hosted on a server somewhere that runs Apache 2 or similar.

Here's a really basic version in the image below, with passwords, table names etc blanked out for security reasons:

The Emic 2 requires that there are no spaces bigger than one single space and no tabs etc.

If you click HERE you'll see the PHP file producing nice coherent, 'intelligent' text such as:

Hello my friend .. how are you doing today? .. .. i am so, sorry .. but the pressure appears to be slowly falling .. .. .. The current time is 02 18 pm .. GMT .. okay .. the last readings were on Tuesday .. at 14:13 hours .. the windspeed was 12.8 miles per hour .. with a maximum gust of 31.1 .. and the wind direction was 247 .. degrees .. The outside temperature was 4.5 .. degrees celcius. .. with a minimum temperature of -0.9 .. and a maximum of 6.5 .. The battery volts was 9.14 .. volts. .. and the humidity was at 98.00 .. per cent .. Thankyou for listening .. and have a nice day .. oh .. by the way .. there are 7 days before the black bin goes out

The final PHP code ended up being a bit more complicated than the above as I wanted more emotional response from the Emic2 and needed some calculations to produce a useful reminder function for my recycling bin:

<?php

 Header("Cache-Control: must-revalidate");
 $offset = 60;
 $ExpStr = "Expires: " . gmdate("D, d M Y H:i:s", time() - $offset) . " GMT";
 Header($ExpStr);

//echo("The current time is:");echo date("h i a");  
//echo(". The current day is:");echo date("d"); 
//echo(". The current month is:");echo date("m"); 
//echo(". Zero indicates not a leap year:");echo date("L"); 

$nowmonth = date("m"); 
$nowday = date("d");

$notLeapYearBinary = date("L"); 
$notLeapYear=28;
if ($notLeapYearBinary==1){$notLeapYear=29;}

if ($nowmonth ==1) {$yearDay=$nowday;}
if ($nowmonth ==2) {$yearDay=$nowday+31;}
if ($nowmonth ==3) {$yearDay=$nowday+31+$notLeapYear;}
if ($nowmonth ==4) {$yearDay=$nowday+31+$notLeapYear+31;}
if ($nowmonth ==5) {$yearDay=$nowday+31+$notLeapYear+31+30;}
if ($nowmonth ==6) {$yearDay=$nowday+31+$notLeapYear+31+30+31;}
if ($nowmonth ==7) {$yearDay=$nowday+31+$notLeapYear+31+30+31+30;}
if ($nowmonth ==8) {$yearDay=$nowday+31+$notLeapYear+31+30+31+30+31;}
if ($nowmonth ==9) {$yearDay=$nowday+31+$notLeapYear+31+30+31+30+31+31;}
if ($nowmonth ==10){$yearDay=$nowday+31+$notLeapYear+31+30+31+30+31+31+30;}
if ($nowmonth ==11){$yearDay=$nowday+31+$notLeapYear+31+30+31+30+31+31+30+31;}
if ($nowmonth ==12){$yearDay=$nowday+31+$notLeapYear+31+30+31+30+31+31+30+31+30;}


//echo(". Current year day is:");echo $yearDay;

$recyclingDay= array(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
$i=1;
while ($i<20)
{
//print "<br>";
$recyclingDay[$i]=3+(($i-1)*21); // Put out recycling bin every 3 weeks on a Tuesday.
//echo("recyclingDay:    ");echo($recyclingDay[$i]);
//echo(" and yearDay:    ");echo($yearDay);
$dayDifference=$recyclingDay[$i]-$yearDay;
//echo(" and DayDifference:    ");echo($dayDifference);
if (($dayDifference>-1)&&($dayDifference<25)){$minimumDayDifference=$dayDifference;}
//echo(" and minimumDayDifference:    ");echo($minimumDayDifference);
$i++;
}
//print "<br>";
$daysTilRecycling=$minimumDayDifference;

if ($daysTilRecycling>0) {$binMessage1=(".. oh .. by the way .. there are ");$binMessage2=(" days before the black bin goes out");}
if ($daysTilRecycling==0){$binMessage1=(" .. oh .. by the way .. the black bin goes out today");$binMessage2=("");}
//echo($daysTilRecycling);


$host="localhost"; // Host name 
$username="XXXXXXXXXXXXX"; // Mysql username 
$password="XXXXXXXXXXXXX"; // Mysql password 
$db_name="XXXXXXXXXXXXX"; // Database name 
$tbl_name="XXXXXXXXXXXXX"; // Table name

// Connect to server and select database.
mysql_connect("$host", "$username", "$password")or die("cannot connect"); 
mysql_select_db("$db_name")or die("cannot select DB");
////////////////////////////////////////////////////////////////////////////

   ?>Hello my friend .. how are you doing today? .. <?   
/////////////////////////////////////////////////////////////////////////////  
   $tempoutmax = -100;
   $tempoutmin = 100;
   $tempout = 0;
   $tempsoil = 0;
   $windgustmax = -100;
   $windgustmin = 100;
   $windemotion="";

   $query3="SELECT * FROM XXXXXXXXXXXXXX ORDER BY id DESC LIMIT 144";
   $result3=mysql_query($query3);
while($row3=mysql_fetch_array($result3))
   {  
   
   if ($row3['tempout'] > $tempoutmax)
   { $tempoutmax = $row3['tempout']; }
   if ($row3['tempout'] < $tempoutmin)
   { $tempoutmin = $row3['tempout']; }
   
   if ($row3['windgust'] > $windgustmax)
   { $windgustmax = $row3['windgust']; }  
   
   }

/////////////////////////////////////////////////////////////////////////////

   $query4="SELECT * FROM XXXXXXXXXXXXXX ORDER BY id DESC LIMIT 6";
   $result4=mysql_query($query4);
   
while($row4=mysql_fetch_array($result4))
   {
   $n=$n+1;
   $age[$n] = $row4['pressure'];  
   }             
     $pressureChange = $age[1] - $age[6];
     $pressureChange = number_format($pressureChange,3);
 //    echo $pressureChange;
  $pressureSlowlyRising=".. oo oo oh baby .. the pressure is slowly rising .. .. ";
  $pressureSlowlyFalling=" .. i am so, sorry .. but the pressure appears to be slowly falling .. .. ";
  $pressureQuicklyRising=".. oo oo oh baby .. the pressure is rising really fast .. ..";
  $pressureQuicklyFalling=" .. i am so, sorry .. but the pressure appears to be falling really fast .. .. ";
    
  if(($pressureChange>0)&&($pressureChange<1)){echo $pressureSlowlyRising;}
  if(($pressureChange<0)&&($pressureChange>-1)){echo $pressureSlowlyFalling;}
  
  if($windgustmax>50){$windemotion=".. oo oo oh baby .. that's very windy";}
  
  if($pressureChange>=1){echo $pressureQuicklyRising;}
  if($pressureChange<=-1){echo $pressureQuicklyFalling;}
////////////////////////////////////////////////////////////////////////////  
// Retrieve data from database 
 
$sql2="SELECT * FROM XXXXXXXXXXXXX  ORDER BY id DESC LIMIT 1";
$result2=mysql_query($sql2);
 
?><?php
// Start looping rows in mysql database.
  //Knots to MPH multiply by 1.15077944802
 while($rows=mysql_fetch_array($result2)){
 ?> .. The current time is <? echo date("h i a"); ?> .. GMT .. okay .. the last readings were on <? echo $day; ?>
 .. at <? echo $hourminutes; ?> hours .. the windspeed was <? echo round($rows['windspeed']*1.151,1); ?> miles per hour .. with a maximum gust of <? echo round($windgustmax*1.15,1); ?> <? echo $windemotion; ?>.. and the wind direction was <? echo $rows['windway']; ?> .. degrees .. The outside temperature was <? echo round($rows['tempout'],1); ?> .. degrees celcius. .. with a minimum temperature of <? echo round($tempoutmin,1); ?> ..  and a maximum of <? echo round($tempoutmax,1); ?> .. The battery volts was <? echo $rows['volts']; ?> .. volts. .. and the humidity was at <? echo $rows['humidity']; ?> .. per cent .. Thankyou for listening .. and have a nice day <? echo $binMessage1;echo($daysTilRecycling);echo $binMessage2; ?>

<?php

//Thankyou for listening .. and have a nice day .. round(520.34345,2);    

// close while loop 
}

?>

<?php
// close MySQL connection 
mysql_close();
 ?>