Skip to content

Instantly share code, notes, and snippets.

@spirilis
Created July 11, 2014 17:15
Show Gist options
  • Save spirilis/022608d3bb9e8b7ed7a1 to your computer and use it in GitHub Desktop.
Save spirilis/022608d3bb9e8b7ed7a1 to your computer and use it in GitHub Desktop.
IRC of Things bot - IrcBot, Enrf24, Pkt on Tiva-C Connected LaunchPad
/* RadioBot - IRC bot with Radio support */
#include <IrcBot.h>
#include <Ethernet.h>
#include <EthernetClient.h>
#include <Enrf24.h>
#include <SPI.h>
#include <Pkt.h>
byte ourMac[] = { 0x52, 0x54, 0xFF, 0xFF, 0xFF, 0x01 };
IrcBot irc;
Enrf24 radio(48, 49, 50);
Pkt rfif(&radio); // Packet processing engine on top of Enrf24 radio
// Add nicknames to this list in order to authorize them to run the "nick" and "io" commands.
const char *authnicks[] = {
"Spirilis",
NULL
};
volatile char playground[4096]; // Arbitrary playground for IRC io rd/wr use.
const uint8_t basestation_rxaddr[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x01};
boolean do_firehose = false;
void setup() {
int i;
IPAddress ip;
Serial.setBufferSize(2048, 64);
Serial.begin(115200);
for (i=0; i < 3; i++) {
delay(1000);
Serial.print(".");
}
Serial.println(" BEGIN!");
Serial.println("Performing DHCP:");
if (!Ethernet.begin(ourMac)) {
Serial.println("Failed to configure Ethernet using DHCP. Halting.");
while(1) delay(1000);
}
Serial.print("Done; IP = ");
ip = Ethernet.localIP();
ip.printTo(Serial);
Serial.println();
Serial.println("Initializing IRC bot:");
Serial.print("adding channel #energia as idx = "); Serial.println(irc.addChannel("#energia"));
//irc.addChannel("#43oh");
irc.attachOnCommand("hi", HandleHi, NULL);
irc.attachOnCommand("die", authnicks, KillBot, NULL);
irc.attachOnCommand("roll", RollOver, NULL);
irc.attachOnCommand("remember", RememberMe, NULL);
irc.attachOnCommand("forget", ForgetMe, NULL);
irc.attachOnCommand("nick", authnicks, ChangeNick, NULL);
irc.attachOnCommand("io", authnicks, HandleMemoryIO, NULL);
irc.attachOnCommand("firehose", authnicks, HandleSwitchOnOff, (void *)&do_firehose);
irc.attachOnCommand("radio", authnicks, HandleRadioCmd, NULL);
irc.attachOnUserJoin("#energia", "Spirilis", MeetAndGreet, "My Master");
//irc.attachOnUserJoin("#43oh", "Spirilis", MeetAndGreet, "My Master");
Serial.println("Issuing irc.begin():");
irc.begin();
SPI.setModule(4);
SPI.setDataMode(SPI_MODE0);
SPI.setBitOrder(MSBFIRST);
radio.begin(250000, 10);
radio.setRXaddress((void *)basestation_rxaddr);
radio.setCRC(true, true);
rfif.begin();
rfif.attachProgram(0x10, HandleThermocoupleData); // TC data will be printed over IRC
}
void loop() {
static uint32_t lastmillis;
uint8_t rfbuf[33];
int len;
if (millis()-lastmillis > 10000) {
Serial.print("botState = "); Serial.println(irc.getStateStrerror());
lastmillis = millis();
}
irc.loop(); // Main worker for IRC bot; runs the whole state machine
// Handle incoming RF data
if (radio.available(true)) {
rfif.loop(); // Run RF Packet RX processor
}
}
void HandleHi(void *userobj, const char *chan, const char *nick, const char *message)
{
char tmpbuf[IRC_NICKUSER_MAXLEN + 32];
Serial.println(">> Executing HandleHi callback handler function");
strcpy(tmpbuf, "Hi there, ");
strcat(tmpbuf, nick);
strcat(tmpbuf, "!");
irc.sendPrivmsg(chan, nick, tmpbuf);
}
void KillBot(void *userobj, const char *chan, const char *nick, const char *message)
{
Serial.println(">> Executing KillBot callback handler function");
irc.sendPrivmsg(chan, NULL, "Bye for now!");
irc.end();
}
void MeetAndGreet(void *userobj, const char *chan, const char *nick) // OnUserJoin doesn't take a message
{
char *override = (char *)userobj, tmpbuf[IRC_NICKUSER_MAXLEN+32];
Serial.print(">> Greeting "); Serial.print(nick); Serial.print(" to "); Serial.println(chan);
strcpy(tmpbuf, "Nice to see you again,");
if (override != NULL) {
if (*override != ',' && *override != ':' && *override != ';')
strcat(tmpbuf, " ");
strcat(tmpbuf, override);
} else {
strcat(tmpbuf, " ");
strcat(tmpbuf, nick);
}
strcat(tmpbuf, "!");
irc.sendPrivmsg(chan, nick, tmpbuf);
}
void RollOver(void *userobj, const char *chan, const char *nick, const char *message)
{
Serial.println(">> Issuing CTCP ACTION rolls over on his belly.");
irc.sendPrivmsgCtcp(chan, "ACTION", "rolls over on his belly.");
}
void RememberMe(void *userobj, const char *chan, const char *nick, const char *message)
{
if (strstr(message, "me") == NULL)
return;
if (!irc.attachOnUserJoin(chan, nick, MeetAndGreet, NULL)) {
irc.sendPrivmsg(chan, nick, "Sorry, my loyalties have been spread thin, and I simply won't ever remember your name!");
}
}
void ForgetMe(void *userobj, const char *chan, const char *nick, const char *message)
{
if (strstr(message, "me") == NULL)
return;
irc.detachOnUserJoin(chan, nick);
}
void ChangeNick(void *userobj, const char *chan, const char *nick, const char *message)
{
char *tmp1;
tmp1 = strstr(message, " ");
if (tmp1 != NULL)
*tmp1 = '\0';
Serial.print(">> Changing NICK to "); Serial.println(message);
irc.setNick(message);
}
/* I/O Memory command subsystem */
const char hexdigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
void *parseTextToPtr(const char *txt)
{
int len = strlen(txt), i = len - 1;
long j;
char c;
uint32_t ptr = 0;
if (strncmp(txt, "0x", 2) != 0) {
// Probably a decimal address value
ptr = atol(txt);
} else {
if (len > 10)
return NULL;
while (i > 1) {
c = txt[i];
if (c >= '0' && c <= '9')
j = c-'0';
else if (c >= 'A' && c <= 'F')
j = c-'A'+10;
else if (c >= 'a' && c <= 'f')
j = c-'a'+10;
else
return NULL;
ptr |= j << ((len-i-1)*4);
i--;
}
}
return (void *)ptr;
}
void convertPtrToText(void *ptr, char *buf)
{
uint32_t lval = (uint32_t)ptr;
int i;
char c[2];
c[1] = '\0';
for (i=0; i<8; i++) {
c[0] = hexdigits[ (lval >> ((7-i)*4)) & 0x0F ];
strcat(buf, c);
}
}
void writePtrResult(const char *chan, const char *nick, const char *varname, void *ptr)
{
char outbuf[128];
Serial.print(">> writing ptr response for var "); Serial.print(varname);
Serial.print(" which is HEX "); Serial.println((uint32_t)ptr, HEX);
strcpy(outbuf, varname);
strcat(outbuf, " = 0x");
convertPtrToText(ptr, outbuf+strlen(outbuf));
irc.sendPrivmsg(chan, nick, outbuf);
}
extern uint32_t _text, _etext, _data, _edata, _bss, _ebss, _end;
void HandleMemoryIO(void *userobj, const char *chan, const char *nick, const char *message)
{
CmdTok args;
void *ptr;
int i, j;
char outbuf[448], *cur = &outbuf[0];
uint8_t a, b;
static boolean write_lock = false;
irc.argToken((char *)message, &args);
if (args.argc < 3) {
// Invalid command
irc.sendPrivmsg(chan, nick, "Invalid syntax; insufficient arguments");
return;
}
if (!strcmp(args.argv[0], "rd")) {
ptr = parseTextToPtr(args.argv[1]);
j = atoi(args.argv[2]);
if (j) {
for (i=0; i < j && i < 148; i++) {
*cur++ = hexdigits[ *((uint8_t *)ptr+i) >> 4 ];
*cur++ = hexdigits[ *((uint8_t *)ptr+i) & 0x0F ];
*cur++ = ' ';
}
*cur = '\0';
irc.sendPrivmsg(chan, nick, outbuf);
}
} else if (!strcmp(args.argv[0], "rda")) {
ptr = parseTextToPtr(args.argv[1]);
j = atoi(args.argv[2]);
if (j) {
for (i=0; i < j && i < 148; i++) {
*cur++ = *((char *)ptr+i);
*cur++ = ' ';
}
*cur = '\0';
irc.sendPrivmsg(chan, nick, outbuf);
}
} else if (!strcmp(args.argv[0], "wr")) {
ptr = parseTextToPtr(args.argv[1]);
// Validate input
for (i = 0; i < args.argc-2; i++) {
Serial.print(">> Validating input byte specifier: "); Serial.println(args.argv[i+2]);
if (strlen(args.argv[i+2]) > 2) {
irc.sendPrivmsg(chan, nick, "Invalid byte found in write stream; aborting");
return;
}
cur = args.argv[i+2];
while (*cur != '\0') {
if (*cur < '0' || (*cur > '9' && *cur < 'A') || (*cur > 'F' && *cur < 'a') || (*cur > 'f')) {
irc.sendPrivmsg(chan, nick, "Invalid byte found in write stream; aborting");
return;
}
cur++;
}
}
// Input validated; commit writes
for (i = 0; i < args.argc-2; i++) {
cur = args.argv[i+2];
b = 0;
for (j=strlen(cur)-1; j >= 0; j--) {
if (cur[j] >= '0' && cur[j] <= '9')
a = cur[j] - '0';
else if (cur[j] >= 'A' && cur[j] <= 'F')
a = cur[j] - 'A' + 10;
else if (cur[j] >= 'a' && cur[j] <= 'f')
a = cur[j] - 'a' + 10;
b |= a << ((1-j)*4);
}
Serial.print(">> Writing hex value "); Serial.print(b, HEX); Serial.print(" to address "); Serial.println((uint32_t)ptr, HEX);
if (!write_lock)
*(((uint8_t *)ptr)+i) = b;
}
if (write_lock)
irc.sendPrivmsg(chan, nick, "Write ignored due to lock.");
else
irc.sendPrivmsg(chan, nick, "Write committed.");
} else if (!strcmp(args.argv[0], "wrlock")) {
if (!strcmp(args.argv[1], "on")) {
write_lock = true;
} else if (!strcmp(args.argv[1], "off")) {
write_lock = false;
}
} else if (!strcmp(args.argv[0], "var")) {
if (!strcmp(args.argv[1], "ptr")) {
if (!strcmp(args.argv[2], "playground")) {
writePtrResult(chan, nick, "playground", (void *)playground);
return;
}
if (!strcmp(args.argv[2], "authnicks")) {
writePtrResult(chan, nick, "authnicks", (void *)authnicks);
return;
}
if (!strcmp(args.argv[2], "_end")) {
writePtrResult(chan, nick, "_end", (void *)&_end);
return;
}
if (!strcmp(args.argv[2], "_bss")) {
writePtrResult(chan, nick, "_bss", (void *)&_bss);
return;
}
if (!strcmp(args.argv[2], "_ebss")) {
writePtrResult(chan, nick, "_ebss", (void *)&_ebss);
return;
}
if (!strcmp(args.argv[2], "_data")) {
writePtrResult(chan, nick, "_data", (void *)&_data);
return;
}
if (!strcmp(args.argv[2], "_edata")) {
writePtrResult(chan, nick, "_edata", (void *)&_edata);
return;
}
if (!strcmp(args.argv[2], "_text")) {
writePtrResult(chan, nick, "_text", (void *)&_text);
return;
}
if (!strcmp(args.argv[2], "_etext")) {
writePtrResult(chan, nick, "_etext", (void *)&_etext);
return;
}
}
}
}
void HandleSwitchOnOff(void *userobj, const char *chan, const char *nick, const char *message)
{
boolean *sw = (boolean *)userobj;
if (!strcmp(message, "on")) {
*sw = true;
}
if (!strcmp(message, "off")) {
*sw = false;
}
}
void HandleRadioCmd(void *userobj, const char *chan, const char *nick, const char *message)
{
char outbuf[256];
CmdTok args;
irc.argToken((char *)message, &args);
if (!strcmp(args.argv[0], "state")) {
strcpy(outbuf, "nRF24L01+ State: ");
strcat(outbuf, radio_state_to_string(radio.radioState()));
irc.sendPrivmsg(chan, nick, outbuf);
}
}
const char * radio_state_to_string(uint8_t status)
{
switch (status) {
case ENRF24_STATE_NOTPRESENT:
return "NO TRANSCEIVER PRESENT";
break;
case ENRF24_STATE_DEEPSLEEP:
return "DEEP SLEEP <1uA power consumption";
break;
case ENRF24_STATE_IDLE:
return "IDLE module powered up w/ oscillators running";
break;
case ENRF24_STATE_PTX:
return "Actively Transmitting";
break;
case ENRF24_STATE_PRX:
return "Receive Mode";
break;
default:
return "UNKNOWN STATUS CODE";
}
}
void HandleThermocoupleData(const uint8_t progID, const int len, const void *buffer)
{
int16_t tcC, cjC, tcF, cjF;
char outbuf[128], tmp[10];
const uint8_t *cbuf = (const uint8_t *)buffer;
if (len != 6)
return; // invalid length
/* Data format:
*
* 1 2 2 1
* +----+--------------+-----------------+-------+
* | ID | TCtemp(C) | ColdJuncTemp(C) | Flags |
* +----+--------------+-----------------+-------+
*/
if (do_firehose) {
strcpy(outbuf, "TC");
itoa(cbuf[0], outbuf+2, 10);
strcat(outbuf, " - ");
memcpy(&tcC, cbuf+1, 2);
memcpy(&cjC, cbuf+3, 2);
tcF = tcC * 9 / 5 + 32;
cjF = cjC * 9 / 5 + 32;
itoa(tcC, tmp, 10);
strcat(outbuf, tmp); strcat(outbuf, "C (");
itoa(tcF, tmp, 10);
strcat(outbuf, tmp); strcat(outbuf, "F) - Cold Junction ");
itoa(cjC, tmp, 10);
strcat(outbuf, tmp); strcat(outbuf, "C (");
itoa(cjF, tmp, 10);
strcat(outbuf, tmp); strcat(outbuf, "F)");
irc.sendPrivmsg("#energia", "Spirilis", outbuf);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment