Communicating bots using Slack

We have commented in the past about my tests with a chatbot (notice that we are not talking here about artificial intelligence, nor natural language processing: we are just thinking about a set of pre-defined commands). You can visit, for example, My second bot. I’m more a believer of the chatbot as a personal assistant than a public-service oriented thing (in Spanish: Un chatbot como asistente personal). For these developments I’m using the Errbot project. They provide a chatbot infrastructure developed in Python, with a friendly community, and a multi-channel approach.

But there is a problem (the world is not perfect): one must choose just a communication channel and that’s all. The developers did not consider the possibility for one bot to be present in several channels. My first attempts tested the XMPP channel but clients for XMPP don’t feel very comfortable and I switched to the Telegram backend: there are apps for several devices, the computer, and so on.
Sometimes I’d prefer to send instructions via a textual interface (maybe the command line) and, for this reason, I decided to test the IRC backend.
Some of us have asked for the multi-channel approach: multibackend, Support multiple backends in one instance but these proposals have not enough supporters.

One solution could be to have two replicated bots listening in two different channels and let them live isolated. But I wanted to try another approach, trying to coordinate bots in some way: we can add a forward command (fw for a shorter version) and then, when I’m in a channel whose bot does not understand some command, I can request it to forward to another bot (which can live in a different channel). For example, if I wanted to know the state of another bot, I could ask to the one I’m communicating to forward the status instruction (for now I have two bots which can understand instrucions starting with ‘!’ and ‘,’, respectively):

!fw ,status

The following step was to implement these ideas in some way. We coul try to communicate bots directly, but this could be a problem if one of them was inactive.
So, why not to try to use a command and control like mechanism by means of some common channel and let the bots read and write there?

We can define a language and let the fw command write to the channel. The bots can read it, and then decide whether the command is for them and execute the commands or not. If some result is obtained, they can write the reply in the channel in order the original requester can read and send it to the user who asked for it.

As a proof of concept I’ve developed err-forward, (link to the version when this post is written. It is a live project and it can evolve in the future). We can see also a video of bots interacting by means of a common channel:

For the development we have used:

  • the plugins mechanism of Errbot
  • the scheduling mechanism of Errbot
  • a common channel for reading and writing commands

Many of us have seen command and control systems based on email, IRC, Twitter,… So, we had to decide one for our bot. I’m not a big supporter of Slack, but I wanted to give it a chance on some project. It has an API, some Python modules (slackclient), so I decided to try it.

We have decided to use three types of messages:

  • Msg for text messages (‘Hello’, ‘Good bye’ and so on)
  • Cmd for commands
  • Rep for replies

The bot needs now the following methods:

  • Activation and initialization: when the bot starts it connects to the channel and sends a ‘Hello’ message
  • Writing: the bot can write a message of any of the types defined before
  • Reading: the bot can read messages posted in the channel and it can identify which ones it can interpret, and act on them. This can produce some writing, when there are replies

We won’t discuss here the details but when a bot has to read and write in a Slack channel, it needs an authorization token in order to use the Slack Web API.
Following previous developments, we will store it in a file called ‘~/.rssSlack‘, with the following format:

[Slack]
api-key:......

On activation, we will use the module configparser, we will connect to the channel and we will send the first message. The channel is a configuration parameter that will be accessed by means of:

    self._check_config('channel') 

We will store the operating system, and the IP of the machine hosting the bot. They will be used in order to send information, when needed. The ‘Hello’ message will include this infomation:

self['sc'] = SlackClient(slack_token)
self['chan'] = str(self._check_config('channel'))
self['userName'] = pwd.getpwuid(os.getuid())[0]
self['userHost'] = os.uname()[1]

self.publishSlack(typ = 'Msg', args = 'Hello! from %s' % self.getMyIP())

We have used the ‘publickSlack‘ method (we will comment on in later).

Then we start the ‘poller’, which will read in an scheduled way the channel in order to see if we can find commands for the bot.

    self.start_poller(60, self.readSlack)

Here we are using the ‘readSlack‘ method (we will comment on it later). In this configuration, the poller is executed each 60 seconds.

The ‘publishSlack‘ method is quite simple, it just prepares the message and posts it on the channel:

 msg = {'userName': usr, 'userHost': host,
            'frm': str(frm), 'typ': typ, 'cmd': cmd, 'args': args }

It includes the username, the host, a ‘frm‘ field which is used by Errbot, a type (‘typ‘) as explained above, and the ‘cmd‘ and ‘args‘ fields that are also used by Errbot (command and arguments). The message is quoted in order to avoid its interpretation as HTML, and it is trasnformed to JSON in order to make it easier to interpret it later. Then it is published on the channel.

    chan = self['chan']
    self['sc'].api_call( "chat.postMessage", channel = chan, text = msgJ)

The reading method ‘readSlack‘ is a bit more complicated: it reads the channel, it needs to decide whether the command is for the bot or not, and then interpret it, execute it, and so on…

Reading the list of messages is easy:

    chan = self.normalizedChan(self._check_config('channel'))
    history = self['sc'].api_call("channels.history", channel=chan)

The we need to see all of them and act on them, if needed:

for msg in history['messages']:
     msgJ = self.extractArgs(msg)
     if ('typ' in msgJ):
         if msgJ['typ'] == 'Cmd':
             # It's a command 
             self.manageCommand(chan, msgJ, msg)
         elif msgJ['typ'] == 'Rep':
             # It's a reply 
             self.manageReply(chan, msgJ, msg)

For this you can see we are using some extra methods.

The ‘extractArgs‘ method is devoted to extract the info from the JSON string. If it is not a message (messages are ignored, for now), it unquotes the arguments (no HTML interpretation):

if msgJ['args'] and (msgJ['typ'] != 'Msg'):
    # Unquoting the args
    self.log.debug("Reply args before: %s " % msgJ['args'])
    tmpJ = urllib.parse.unquote(msgJ['args'])
    msgJ['args'] = tmpJ

The ‘manageCommand‘ method decides if it is a command for our bot and tries to execute it:

listCommands = self._bot.all_commands
if msgJ['cmd'].startswith(self._bot.bot_config.BOT_PREFIX):
    cmd = msgJ['cmd'][len(self._bot.bot_config.BOT_PREFIX):]

    self.log.debug("Cmd: %s"% cmd)
    if cmd in listCommands:

That is, it gets the list of commands available in the bot, it checks if the command starts with the adequate character, and then it verifies if it is a command for the bot. The it executes it. For this, it gets the adequate method:

    method = listCommands[cmd]

The execution is as follows:

    replies = method("", msgJ['args'])

Depending of wheter it is a generator or not, the management is slightly different.
In both cases it stores the replies as a text in order to write it in an adequate way:

   self.publishSlack(typ = 'Rep', usr= msgJ['userName'],
       host=msgJ['userHost'], frm = msgJ['frm'], args = txtR)

In some cases there can be templates for the replies, so we need to apply them:

    txtR = txtR + tenv().get_template(method._err_command_template+'.md').render(reply)

After this, the bot deletes the command from the channel (Slack uses for this an identificator, which is based on the posting time).

    self.deleteSlack(chan, msg['ts'])

Finally, the message could be a reply. For this we have the method ‘manageReply‘. This method needs to identify if the reply is for our bot:

if ((msgJ['userName'] == self['userName'])
        and (msgJ['userHost'] == self['userHost'])):
    # It's for me
    self.log.info("Yes. It's for me")
    replies = msgJ['args']

And send it back to the user who sent the command:

    self.send(msgTo, replies)

Finally, it deletes the reply:

    self.deleteSlack(chan, msg['ts'])

We have still a command pending: the ‘forwardCmd‘ method. A command can have arguments, so we need to split them:

if args.find(' ') >= 0:
     cmd, argsS = args.split()
</code>

It publishes the message in the channel:


self.publishSlack(mess=mess,
        usr=self['userName'], host= self['userHost'],
        typ = 'Cmd' , cmd = cmd, args = argsS)

We have defined two aliases, ‘fw‘, and ‘forward‘.

In order to use this plugin we can load it from the GitHub repo for each bot:


!repos install https://github.com/fernand0/err-forward

We need to define a channel in Slack and then we can configure it in the bots:


!plugin config ErrForward {'channel': the name}

Some final thougths:

  • We are proposing here Slack but any channel could be used, with adequate modifications. Maybe there are better options.
  • It would be nice to have different channels available. We could improve the plugin and we could allow to select the channel by means of a configuration parameter. Maybe it would be overkill to establish another plugin infrastructure in Errbot in order to have several channels available. Anyway, it would be nice to see improvements over these ideas, or code for other forwarders.
  • The forwarding is done by writing the complete instrucion (‘!’, ‘,’ including the bot character). We could avoid it, but it has not been our objective here
  • It there are several bots able to execute some instruction, they can do it. There is no locking mechanism. This could be a problem when the second one would try to delete the message.
  • We have succesfully tested this with several bots (two) which have been able to execute commands and establish some communication
  • We have learnt some internalities of Errbot which allow for the execution of commands, apply templates, and so on

I’ve been trying this for several weeks and it seems to be working for my plugins (one bot is listening on Telegram, and the other one is listening in an IRC channel). It seems to be robust for these plugins, which can be found at my repos (fernand0’s GitHub). All my modules’ name start with ‘err-‘.

This post was written originally in Spanish, you can read it at: Comunicación de bots con Slack. As my English is far from perfect, you can contact me if you need some assistance.

Advertisements

Storing credentials of your Python programs in the keyring

err_speech

As I introduced in previous posts I’m a happy user of ErrBot (My second bot).
My main concern was to be able to control some functions in some machines without having to connect via ssh or exposing web pages to the world.

I couldn’t feel comfortable with the idea of storing my credentials in the config.py file which is the suggested way to connect to the services with the backend selected (‘username’ and ‘password’).

In order to avoid this I’m proposing here an alternative approach, based on the keyring module. Provided you have it correctly installed, you can store your credentials there and use them from your Python programs. In this case, from ErrBot.

You can see there the way to store your credentials and so on, we will not replicate them here.

Notice that the credentials storage will rely on the operating system and depending on the configuration anybody logged into your account will have access to them without password (you’ll need this if you want a bot that can start in an unattended way). We are only obtaining the added security of not having the credentials stored in the config.py file but not much more.

Then you need to make some changes in the config.py file. First of all, importing the module:

import keyring

Later, before the BOT_IDENTITY section you can add three variables, for the server (needed in order to select the account in the keyring) and the username:

server = 'jabber-fernand0movilizado'
username = 'fernand0movilizado@gmail.com'
password = keyring.get_password(server,username)

Finally, in the BOT_IDENTITY structure, in the backend you have selected, you
can put (XMPP backend, for example):

    'username': username,  # The JID of the user you have created for the bot
    'password': password,       # The corresponding password for this user

In this way when the bot starts it gets the credential from the keyring. You can use a similar approach in your programs and, if you do not need to autostart the program you can protect with a password the keyring entries.

A robotic leg

This post was more of a note to self than something of actual value. Anyway there were some advances, some tabs open in the browser and I felt it was better to try to share them here for future reference than waiting for an (eventual) finishing point of the project.
Maybe it will be useful for somebody.

In a mobile camera we anticipated the idea of making something that can move. In fact, there was an inspiration in Stubby. Full featured, miniature hexapod. There is more info in Stubby the (Teaching) Hexapod.

My only (and not small) problem with that design were the manual abilities and the tools needed: wooden cutting, mechanization,… I was wandering (physically and mentally) about my possibilities and one of the solutions was to use wood sticks; I’d need to cut and make perforations but that was not too scaring for me.

Internet is plenty of projects such as A spider called “Chopsticks” that is using chopsticks for the legs and Popsicle Stick Hexapod R.I.P.. Their ideas were similar to my own ones and they gave me some encouragement. I also had dicovered Build a 12-Servo Hexapod. It has some limitations but shows some interesting ideas.
Just to comply with my initial statement (more tabs!), we can see some more proyects like
Hexpider with a different design (it can even write!) and 6-legged robot project. All of them have helped me providing insight and ideas about the movement and articulations (at a very basic level, some elaboration is needed that will be shown in further posts).

With these ideas I visited a DIY store in order to get inspiration. I forgot quickly the idea of wooden sticks because I discovered some plastic tubes that seemed to me more convenient: they should be easier to cut and they should be lighter. You can find also alluminun sticks that would have a nicer look, but at this stage of the project the plastic tubes seemed easier to use.

View this post on Instagram

Palo

A post shared by Fernando Tricas García (@ftricas) on

My supposition was correct and this material is easy to manage: we can make holes and fix the servo with a screw, as it can be seen in the following image:

View this post on Instagram

La pata #raspi #servo

A post shared by Fernando Tricas García (@ftricas) on

The picture is not very good, but it should be enough to get the idea about joining the different parts. I’m very grateful for similar pictures from other projects that provided hints about how to proceed. As you can see I’ve chosen a design wit three servos for each leg.

We have used cable ties for joining some parts, maybe we’ll need some better methds to improve these unions. It should be easy to make more ‘agressive’ operations if needed.

It was quite surprising to see how fast I could configure the leg with these tools, we will see if I can go so fast in the future (hint: no).

For the movement of the legs we had some experience with servos (Adding movement: servos). The whole code was rewritten following the ideas of PiCam.

On the software side, I will only show a couple of small programs that can be found at servo.

The first one can move each joint in and independent way (we wanted to be able to test them from the command line legOneMove.py.

We have the three joints associated to three GPIO ports:

servoGPIO=[17, 23,15]

and we will use a function for the transormation of an angle in the needed pulse:

def angleMap(angle):
   return int((round((1950.0/180.0),0)*angle)/10)*10+550

The movement function is very simple:

def movePos(art, pos):
    servo = PWM.Servo()
    print art
    servo.set_servo(art, angleMap(pos))
    time.sleep(VEL)

Shame on me, I discovered that I was needing the last delay because when the program finishes it stops sending the needed pulses and the movement is not completed.

Finally, in

movePos(servoGPIO[int(sys.argv[1])], int(sys.argv[2]))

we are passing as the first argument the joint we are moving (mapped to the adequate GPIO). The second argument is the angle. Notice that no bound nor limit checking is done so, some bad things can happen if the parameters are not adequate.

The second program is legServo.py. It is a simulation of the movements needed for the leg in order to walk: raise the leg, move forward, lower it and move it backwards, and so on…
Some better movements will be needed in the future but do not forget that this is just a proof of concept.

Now we can see a video with a sequence of these movements repeated several times that I recorded with my son’s help.

View this post on Instagram

En movimiento #servo #raspi

A post shared by Fernando Tricas García (@ftricas) on

We can now see another video with some previous tests, taking advantage of the wonderful YouTube video editor, with two joints and with three joints:

The next steps will be to construct the other legs (four or six) and we’ll need to see if we need some more hardware (may be we will need some more input/ouputs in order to control all the servos for the legs and maybe something more). We will need also something for the ‘body’.

This post was published originally in Spanish, at: Una pata robótica.