Jump to content

PHP IRC Bot controller for Arduino


rotozip

Recommended Posts

I have created an IRC bot that allows users to enter various commands in a channel which is then sent to an Arduino controller. I currently have connected the Arduino to a DB serial connector which then connects to a CM17a Firecracker X10 device. An example of what I can do with this is turn on and off lights from an IRC channel. This might be a fun thing to do in the hakHouse. The code is kind of rough right now but it works.

Here is a photo of the Arduino->DB9->CM17a:

post-12062-1228601187_thumb.jpg

Sorry the blurry picture.

Here is the php bot script:

#!/usr/bin/php
<?php

//
//Author:    Randy Greeen aka rotozip
//            www.slappingturtle.com
//Date:        12/05/08
//

/**********************
* Start configuration *
**********************/

//IRC stuff
$debug = false;

//Because of the way ustream has set up IRC set isUstream to true if your bot runs on ustream.
//For any standard IRC set isUstream to false
$isUstream = false;

$IRCServer = "irc.wyldryde.org";
$IRCPort = 6667;

$channel = "#channel";
$nick = "controlbot";
$password = "password";

//Set mustBeVoiced to true if user must be voiced to send commands 
$mustBeVoiced = false;

//Seconds to queue each message.
$waitTime = 20;

//Arduino stuff
$arduino = "/dev/cu.usbserial-A4000Q4s3";

/********************
* End configuration *
********************/
$messageQueue =  NULL;
$messageType = "";

if ($isUstream) {
    $messageType = "PRIVMSG " . $channel;
} else {
    $messageType = "NOTICE ";
}

// Prevent PHP from stopping the script after 30 sec
set_time_limit(0);

function queueMessage($socket, $user, $message) {
    global $messageQueue, $counter, $waitTime, $waitTimeMili, $messageType;

    $index = count($messageQueue);
    
    $messageQueue[$index]["user"] = $user;
    
    $messageQueue[$index]["message"] = $message;    
    
    if(count($messageQueue) > 0 ) {
        if(count($messageQueue) == 1 ) {
            $timeTilSent = ($waitTimeMili - ($counter % $waitTimeMili)) / 10;    
        }else{
            $timeTilSent = $waitTime + (($waitTimeMili - ($counter % $waitTimeMili)) / 10);
        }
    }
    
    fputs($socket, $messageType . " " 
        .$user. " : Your command [" 
        .$messageQueue[$index]["message"] . 
        "] has been queued and will be sent in "
        .$timeTilSent." seconds.\n");
    echo '(' . date('H:i:s') . ') ->' . " Queued message [" 
        .$messageQueue[$index]["user"] .  "][" 
        .$messageQueue[$index]["message"] . "] will be sent in "
        .$timeTilSent. " seconds\n";
    return true;
}

function sendMessage($socket) {
    global $messageQueue, $messageType;
    
    fputs($socket, $messageType . " " 
        .$messageQueue[0]["user"]. " : Your command [" 
        .$messageQueue[0]["message"] . "] has been sent.\n");
    echo '(' . date('H:i:s') . ') ->' . " Send message [" 
        .$messageQueue[0]["user"] .  "][" 
        .$messageQueue[0]["message"] .  "]\n";
    
    arduino($messageQueue[0]["message"]);
    
    if(count($messageQueue) > 0 ) {
        $temp = array_shift($messageQueue);
    }
    
    return true;
}

function checkCharacters($message) {

    $charsIn = array("<", ">", "&", "`", '"', "'");
    $charsOut   = array("<", ">", "&", "'", """, "'");
    $newMessage = str_replace($charsIn, $charsOut, $message);
    
    return $newMessage;
}

function arduino($message) {
    global $arduino;
    
    echo '(' . date('H:i:s') . ') ->' . " Sending command " 
        .$message. " to Arduino at " 
        .$arduino.  "\n";
    
    system("echo $message > $arduino", $retVal);
    
}

// Opening the socket to the irc network
$socket = fsockopen($IRCServer, $IRCPort);

stream_set_timeout($socket, 0, 1);

// Send auth info
fputs($socket,"USER ".$nick." slappingturtle.com ST :" .$nick. " bot\n");

fputs($socket,"NICK ".$nick."\n");

if($isUstream) {
    //for c.ustream.tv
    fputs($socket,"PASS ".$password."\n");
}else{
    //for other irc
    fputs($socket,"NICKSERV :identify ".$password."\n");
}

sleep(3);

// Join channel
fputs($socket,"JOIN ".$channel."\n");

$counter = 0;
$buffer = null;
$trackNames = false;
$waitTimeMili = ($waitTime * 10);

echo '(' . date('H:i:s') . ') ->' . " Message queue timer set for " .$waitTime. " seconds\n";

while(!feof($socket)) {
    
    usleep(100000);

    // Timer Stuff
    // 1 minute is 600 ms
    
    if ($counter % $waitTimeMili == 0 && $counter != 0) {
        echo '(' . date('H:i:s') . ') ->' . " Message queue timer has elapsed\n";
        if(count($messageQueue) > 0 ) {
            sendMessage($socket);
            $counter = 0;
        }
    }    
    
    $counter++;
    
    // Reset counter after 2 hours (7200 seconds with 10 counts per second)
    if ($counter == 72000) {
        $counter = 0;
    }
    // End Timer Stuff

    $buffer .= fgets($socket, 512);

    while (substr_count($buffer, "\n") != 0) {
        // Get the end of the current line
        $offset = strpos($buffer, "\n");
        // Get the current line
        $data = substr($buffer, 0, $offset);
        
        if($debug)
            echo '(' . date('H:i:s') . ') -> ' . $data . "\n";
        
        // Remove it from the buffer
        $buffer = substr($buffer, $offset + 1);

        flush();
        
        //Track data coming back from NAMES command
        if ($trackNames) {
            
            $replyCode = explode(" ", $data);
            if ($replyCode[1] != "366") {
                $userList = explode(" ", $data);
                //print_r($userList);
                for ($i = 0; $i <= count($userList); $i++) {
            
                    $userV = '+'.$userToTrack;
                    $userOp = '@'.$userToTrack;
                    $userHop = '%'.$userToTrack;
                    if(($userList[$i] == $userV) || ($userList[$i] == $userOp) || ($userList[$i] == $userHop) || ($mustBeVoiced == false)) {
                        echo '(' . date('H:i:s') . ') ->' . " User " .$userToTrack.  " authorized\n";
                        $found = true;
                        $trackNames = false;
                        queueMessage($socket, $userToTrack, $messageToTrack);
                        break;
                    }else{
                        $found = false;
                    }
                }    
            }
            
            if(!$found && ($replyCode[1] == "366")) {
                fputs($socket, $messageType . " " 
                    .$userToTrack. " : Only \"voiced\" users may send control commands, due to a matter of trust. Remember, to become a " .
                    "voiced user, you must be a chat room regular (behave, play nice, help others, be here longer than a day, etc.)... \n");
                echo '(' . date('H:i:s') . ') ->' . " User " . $userToTrack .  " not authorized\n";
                $trackNames = false;
            }    
        }
        
        // Separate all data
        $ex = explode(' ', $data);

        // Send PONG back to the server
        if($ex[0] == "PING"){
            fputs($socket, "PONG ".$ex[1]."\n");
            echo '(' . date('H:i:s') . ') ->' . " PING\n";
        }

        $usersNic = split("!", $ex[0]);

        $user = trim($usersNic[0], ":");

        $commandFound = str_replace(array(chr(10), chr(13)), '', $ex[3]);

        switch ($commandFound) {
            case ":!" .$nick:
                $text = explode($commandFound, $data);
                $message = trim($text[1]);
                if(strlen($message) > 0 && preg_match("/[0-9]/", $message)) {
                    fputs($socket,"NAMES ".$channel."\n");
                    $trackNames = true;
                    $userToTrack = $user;
                    $messageToTrack = $message;
                }else{
                     fputs($socket, $messageType . " " 
                         .$user. " : To send a command type !" 
                        .$nick. " <Command to send>.  Valid command numbers are 0-9.  For other ".
                         "available commands type !" .$nick. "help.\n");
                }
                break;
            case ":!" .$nick. "help":
                fputs($socket, $messageType . " " 
                    .$user. " : Commands are !" 
                    .$nick. ", !" 
                    .$nick. "help, and !" 
                    .$nick. " <Command to send>.  Valid command numbers are 0-9.\n");
                echo '(' . date('H:i:s') . ') ->' . " User " 
                    .$user.  " issued the " 
                    .$commandFound. " command\n"; 
                break;
            case ":!" .$nick. "about":
                fputs($socket, $messageType . " "
                    .$user. " : With the " 
                    .$nick. " bot you can send commands to a device to control stuff.  " .
                    "This script was created by Randy Green aka rotozip http://www.slappingturtle.com\n");
                echo '(' . date('H:i:s') . ') ->' . " User " 
                    .$user.  " issued the " 
                    .$commandFound. " command\n";
                break;        
        }
    }
}

?>

Here is the Arduino sketch:

/* Arduino Interface to the CM17A Wireless X10 dongle. BroHogan 7/19/08
 * Modified by Randy Green aka rotozip 12/6/08
 * The CM17A gets it power and data using only the RTS, CTS, & Gnd lines.
 * A MAX232 is not req. (0/+5V work OK) If MAX232 IS used reverse all HIGHs & LOWS
 * Signal      RTS DTR        Standby | '1' | Wait | '0' | Wait | '1' | Wait...
 * Reset        0   0         _____________________       _____________________
 * Logical '1'  1   0   RTS _|                     |_____|
 * Logical '0'  0   1         ________       ___________________       ________
 * Standby      1   1   DTR _|        |_____|                   |_____|
 *
 * MINIMUM time for the '1', '0' and 'Wait' states is 0.5ms.
 * At least one signal must be high to keep CM17A powered while transmitting.
 * Each xmit is 40 bits -> "Header" 16 bits,  "Data" 16 bits, "Footer" 8 bits
 * CONNECTION: RTS -> DB9 pin 7.  DTR -> DB9 pin 4. Gnd. -> DB9 pin 5.
 */

#define RTS_pin 2                   // RTS line for C17A - DB9 pin 7
#define DTR_pin 3                   // DTR line for C17A - DB9 pin 4
#define BIT_DELAY 1                    // ms delay between bits (0.5ms min.)
#define ON     0                       // command for ON
#define OFF    1                       // command for OFF
#define BRIGHT 2                       // command for 20% brighten
#define DIM    3                       // command for 20% dim

int incomingByte;

unsigned int houseCode[16] = {
  0x6000,  // A
  0x7000,  // B
  0x4000,  // C
  0x5000,  // D
  0x8000,  // E
  0x9000,  // F
  0xA000,  // G
  0xB000,  // H
  0xE000,  // I
  0xF000,  // J
  0xC000,  // K
  0xD000,  // L
  0x0000,  // M
  0x1000,  // N
  0x2000,  // O
  0x3000,  // P
};

unsigned int deviceCode[16] = {
  0x0000,  // 1
  0x0010,  // 2
  0x0008,  // 3
  0x0018,  // 4
  0x0040,  // 5
  0x0050,  // 6
  0x0048,  // 7
  0x0058,  // 8
  0x0400,  // 9
  0x0410,  // 10
  0x0408,  // 11
  0x0418,  // 12
  0x0440,  // 13
  0x0450,  // 14
  0x0448,  // 15
  0x0458,  // 16
};

unsigned int cmndCode[] = {
  0x0000,  // ON
  0x0020,  // OFF
  0x0088,  // 20% BRIGHT (0x00A8=5%)
  0x0098,  // 20% DIM    (0x00B8=5%)
};

void setup() {
  pinMode(RTS_pin,OUTPUT);             // RTS -> DB9 pin 7
  pinMode(DTR_pin,OUTPUT);             // DTR -> DB9 pin 4
  Serial.begin(9600);                  // use the serial port to send the values back to the computer
}

void loop(){   // Sample Commands
/*
 xmitCM17A('A',5,ON);
 xmitCM17A('A',5,OFF);
 xmitCM17A('A',5,DIM);
*/
  //delay(2000);
  if (Serial.available() > 0) {
    // read the incoming byte:
    incomingByte = Serial.read();
    //Serial.print("I received: ");
    //Serial.println(incomingByte);    
    switch (incomingByte) {
      case 48: //0
        for(int i=1; i<10; i++) {
          //Serial.print("Looping ");
          xmitCM17A('A',i,OFF);
        }
    break;
      case 49: //1
        xmitCM17A('A',1,ON);
    break;
      case 50: //2
        xmitCM17A('A',2,ON);
    break;
      case 51: //3
        xmitCM17A('A',3,ON);
    break;
      case 52: //4
        xmitCM17A('A',4,ON);
    break;
      case 53: //5
        xmitCM17A('A',5,ON);
    break;
      case 54: //6
        xmitCM17A('A',6,ON);
    break;
      case 55: //7
        xmitCM17A('A',7,ON);
    break;
      case 56: //8
        xmitCM17A('A',8,ON);
    break;
      case 57: //9
        xmitCM17A('A',9,ON);
    break;
    }
  } 
} 

void xmitCM17A(char house, byte device, byte cmnd){
  unsigned int dataBuff = 0;
  byte messageBuff[5];

  // Build Message by ORing the parts together. No device if Bright or Dim
  if(cmnd == ON | cmnd == OFF){
    dataBuff = (houseCode[house-'A'] | deviceCode[device-1] | cmndCode[cmnd]);
  }
  else dataBuff = houseCode[house-'A'] | cmndCode[cmnd];

  // Build a string for the whole message . . .
  messageBuff[0] = 0xD5;               // Header byte 0 11010101 = 0xD5 
  messageBuff[1] = 0xAA;               // Header byte 1 10101010 = 0xAA 
  messageBuff[2] = dataBuff >> 8;      // MSB of dataBuff
  messageBuff[3] = dataBuff & 0xFF;    // LSB of dataBuff
  messageBuff[4] = 0xAD;               // Footer byte 10101101 = 0xAD

    // Now send it out to CM17A . . .
  digitalWrite(DTR_pin,LOW);           // reset device - both low is power off
  digitalWrite(RTS_pin,LOW);
  delay(BIT_DELAY);

  digitalWrite(DTR_pin,HIGH);           // standby mode - supply power
  digitalWrite(RTS_pin,HIGH);
  delay(35);                           // need extra time for it to settle

  for (byte i=0;i<5;i++){
    for( byte mask = 0x80; mask; mask >>=1){
      if( mask & messageBuff[i]) digitalWrite(DTR_pin,LOW);  // 1 = RTS HIGH/DTR-LOW
      else digitalWrite(RTS_pin,LOW);                        // 0 = DTR-HIGH/RTS-LOW
      delay(BIT_DELAY);                // delay between bits

      digitalWrite(DTR_pin,HIGH);      // wait state between bits
      digitalWrite(RTS_pin,HIGH);
      delay(BIT_DELAY);

    }  
  }
  delay(1000);                         // wait required before next xmit
}

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...