Coder Social home page Coder Social logo

davesnowdon / naoutil Goto Github PK

View Code? Open in Web Editor NEW
9.0 4.0 6.0 103 KB

A project containing useful utility code for NAO developers (mostly in python). Includes support for i18n and JSON serialisation of arbitrary classes

License: GNU Lesser General Public License v3.0

Shell 1.14% Python 98.86%

naoutil's Introduction

naoutil

A project containing useful utility code for NAO developers (mostly in python). Includes support for i18n and JSON serialisation of arbitrary classes

NaoEnvironment

NaoEnvironment provides a convenient way to access ALProxy instances (ALMemory, ALMotion etc) without having to manually create them. It also provides access to logging. The intent is to create an instance by calling the make_environment() function passing in a reference to a choreographe box. It's then possible to get the following functionality

  • logging, via the log() method
  • the path to the resources dir (assuming that the "resources" dir is at the sa e level as the src dir under which naoutil is located) using the resources_dir() method.
  • the name of the current language using, curent_language()
  • the 2-letter ISO code of the current language via current_language_code()
  • localised test strings from property files using the localized_text() method and passing in the base name of the properties file and a key
env = make_environment(choreographe_box)
lc = env.current_language_code()
env.log("Current language is " + lc)
env.memory.getData("ALMemory location")
# this assumes the resources dir contains a properties with a basene of
# defaults, such as defaults_XX.properties or defaults_XX.json where
# XX is a 2-letter ISO language code and that these files have the key "hello"
lt  = env.localized_text("defaults", "hello")

i18n

Choreographe boxes that allow text to be retrieved from files for easier management of i18n issues - main aim is to allow clean separation of code and "user visible" strings or other data that depends on the current language of the robot and so aid in translating NAO applications.

The idea is that all localised files are stored in a standard directory at the same level as behaviour.xar (I use resources). Each filename is composed of 3 pieces:

  • basename - any name you choosce
  • the two letter ISO code for the language of the text in the file
  • an extension .properties for properties files and .txt for plain text files used for the random text functions

This code supports three operations

  • getting a localised string from a properties file
  • retrieving a random string from a text file (one string per line) and retrieving a random string from a property in a properties file (in this case a single string is split using a separator character and one of the split strings is chosen at random) or a JSON file (in which case the JSON file can encode the values as a list)
  • basic support for java format property files using a modifed version of jprops (from https://github.com/mgood/jprops)

The idea behind the random text is that there are often places in a NAO app where you want NAO to say something but it gets a bit boring if he always says the same thing so this provide an easy solution and there is no need to change the choreographe or python code to add more options.

For example here is what a java properties file might look like:

hello=Hello
attractAttention=Excuse me!/Oy! You over there!/Hey! I want to talk to you./Human, I wish to talk to you./You will be interrogated! Please!

and here's the same content in JSON format

{
   "hello" : "Hello",
   "attractAttention" : [
       "Excuse me!",
       "Oy! You over there!",
       "Hey! I want to talk to you.",
       "Human, I wish to talk to you.",
       "You will be interrogated! Please!"
   ]
}

The code will transparently use JSON or java properties format as long as properties files have a .properties extension and JSON files have a .json extension.

There are two ways to use this code:

###1) python Look at the unit tests to get a better idea of how this works.

import util.i18n as i18n
# get the value of the property "hello" property in English from the 
# basename "defaults"
hello_value = i18n.get_property(self.resources_path, "defaults", "en", "hello")

# get a list of strings (options for random text) from the basename "defaults"
# in chinese from the property "attractAttention" separated by "/"
options = i18n.read_text_options(self.resources_path, "defaults", "zh", "attractAttention", "/")

# read the options from the plain text file with basename "example" in French
options = i18n.read_text_options(self.resources_path, "example", "fr")

###2) Choreographe boxes Localized Text Property

  • resourceDir - directory where files are located relative to behaviour.xar, I prefer "resources"
  • resourceFile - basename (without language code or extension) of file to load
  • resourceProperty - the name of the property to select from the properties file

Random Localized Text

Picks a random string from a text or properties file. Takes four parameters

  • resourceDir - directory where files are located relative to behaviour.xar, I prefer "resources"
  • resourceFile - basename (without language code or extension) of file to load
  • resourceProperty - blank if using a plain text file otherwise the name of the property to select from the properties file
  • resourceSeparator - separator to use when splitting strings from a property. If blank defaults to / Has no effect when reading plain text files

The project contains some example uses of these boxes.

In this project I've combined use of choreographe for the boxes and eclipse/pydev to manage the python code outside of choreographe. The eclipse project is inside the choreographe one and I've followed the maven directory structure so that the main python code is in src/main/python and the unit tests are in src/test/python. You'll need to include at least the src/main dir in your projects for the choreographe boxes to work

JSON

Provides functions to_json_string(), to_json_file() and from_json_string(), from_json_file() that allow arbitrary classes to be converted to/from JSON providing that they implement 2 helper functions. The classes may also need to implement the __eq__function and name if these aren't defined in sub-classes. The two functions below demonstrate this. JsonTestBase is a class with no data but which can be serialised and deserialised so you get back a class of the same type, JsonWithData is a more interesting data-bearing class.

class JsonTestBase(object):
    def __init__(self):
        super(JsonTestBase, self).__init__()
    
    def name(self):
        return self.__class__.__name__

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

    # used to support JSON serialisation of custom classes
    def to_json(self):
        return { }

    # used to enable this class & sub types to be reconstituted from JSON
    @classmethod
    def from_json(klass, json_object):
        return klass()

class JsonWithData(JsonTestBase):
    def __init__(self, source, sensorData):
        super(JsonWithData, self).__init__()
        self.source = source
        self.sensorData = sensorData

    # used to support JSON serialisation of custom classes
    def to_json(self):
        return { 'source' : self.source,
                 'sensorData' : self.sensorData}

b = JsonWithData('foo', { 'a': 123, 'b' : 456})
json = to_json_string(b)
rev = from_json_string(json)

Broker

Provide an easy-to-create ALBroker. It auto-detects the IP/port of a NaoQi available somewhere on the network. This makes it possible for a developper to distribute behaviours creating their own broker without having to care about providing valid IPs/ports info.

It can be used in two different ways. The first is using the broker.create() method. It returns a context manager. This mean you can use it with a 'with' statement:

from naoutil import broker

with broker.create('MyBroker') as myBroker:
    ... # Code running inside the broker.

... # Outside of the with-block the broker is automatically shutdown. Even in case of an exception in your code (which will be raised back to you).

Or you can use the Broker class of the broker module:

from naoutil import broker

myBroker = broker.Broker('MyBroker')
... # Code running inside the broker.
myBroker.shutdown()

You can also provide the same parameters the original ALBroker class has, except here they are optional (and named):

  • broker_ip: The IP on which the broker will listen. Be careful, listening only on 127.0.0.1 when your broker is not running on the robot will prevent you from subscribing to ALMemory events.
  • broker_port: 0 let the broker find itself a free port.
  • nao_id: An "ID" corresponding to a NAO robot. It can be an IP address, or a Bonjour/Avahi address, or a hostname, or a robot name (case sensitive).
  • nao_port: Port used by the NaoQi process.

If you don't provide one of this parameter, it will be auto-detected. This means you could for instance just give nao_id='nao.local' or nao_id='nao'. It will resolve itself the nao_port, broker_ip and broker_port.

It is STRONGLY adviced to users of naoutil to not give any nao_id/nao_port arguments. It is better to let naoutil.broker module find the right NAO. Unless you have a user interface to let the end-user select its NAO robot in a list (see later, Finding NAO robots on the network).

If your script calling a naoutil.broker is executed on a robot, it will connect to the local NaoQi.

If your script is executed on a computer or mobile device, and the end-user has only one robot, it will find it.

And in case an end-user has several robots, this person can set the FAVORITE_NAO environment variable to guide naoutil.broker module into finding its prefered NAO robot.

The auto-detection algorithm uses Bonjour/Avahi through DBus to find available NaoQis. The detection procedure goes as follow:

  • If a nao_id is provided but not a nao_port,
    • If the nao_id correspond to an Avahi entry, get the nao_port and resolve the IP of the robot.
    • If there is no NaoQi with this ID on Avahi, it uses the default port, '9559', and the provided ID as a network address.
  • If a nao_id is not provided,
    • If the environment variable FAVORITE_NAO is set (with something similar to a nao_id argument) and there is a corresponding robot available on the network, use it.
    • Otherwise, if Avahi returns a NaoQi running locally on the machine, use it.
    • Otherwise, get the first NaoQi Avahi can find.
    • If no NaoQi can be found by Avahi, use the default 'nao.local' IP address and '9559' port.
  • If no broker_ip is given, try to find the IP of the network card routing to the detected nao IP.
  • If no broker_port is given, use 0 (let the broker find itself a free port).

ALModule

A wrapper around ALModule is provided in the naoutil package. It adds two significant features.

First, you don't need to declare your ALModule in the globals of your script. Neither you need to set the name of this global equal to the module name. This wrapper will do it all for you. Therefore, you can create new ALModule anywhere you want in your code: inside functions, modules, classes, etc.

Second, you don't need to provide a module name. The wrapper will create it for you, based on the fully qualified name of your sub-class.

from noaoutil.module import Module

class MyModule(Module):
    def __init__(self):
        Module.__init__(self)
        # Or
        Module.__init__(self, 'module_name')
        
def myFunction()
    aModule = MyModule()
    
myFunction()

Memory callbacks

This feature allows you to make callbacks on ALMemory events or micro-events without having to make-up a module for that. You just need to provide the event name and a callback function. The callback function can also be a python lambda.

from naoutil import memory

# Run inside a behaviour environment (ie. choregraphe box) or inside a self created broker.

def my_event_callback(data_name, value, message):
    print 'Event', data_name, value, message

memory.subscribeToEvent('RightBumperPressed', my_event_callback)
raw_input("Press ENTER to stop subscribing to RightBumperPressed\n")
memory.unsubscribeToEvent('RightBumperPressed')

Available methods are:

  • Events
    • subscribeToEvent(dataName, callback)
    • unsubscribeToEvent(dataname)
  • Micro-events
    • subscribeToMicroEvent(dataName, callback, cbMessage='')
    • unsubscribeToMicroEvent(dataName)

Finding NAO robots on the network

You can get a list of NAO robots or NaoQi running on computers through Bonjour/Avahi.

from pprint import pprint
from naoutil import avahi

list_of_robots = avahi.find_all_naos()
pprint(list_of_robots)

Each entry of the list is a dictionary with the following keys:

  • robot_name: The name of the robot (string).
  • host_name: The hostname of the robot (string).
  • ip_address: The IP address corresponding to the hostname (string).
  • naoqi_port: The port used by NaoQi (int).
  • local: If NaoQi run on the same machine than us or not (boolean).
  • favorite: If the environment variable FAVORITE_NAO correspond to this robot (boolean).

naoutil's People

Contributors

axelvoitier avatar davesnowdon avatar dnajd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

naoutil's Issues

Backward compatibility with previous argument names for broker creation.

Following PEP-8 compliant renaming, following argument names of broker.Broker() and broker.create() are not available anymore:
brokerIp => broker_ip
brokerPort => broker_port
naoIp => nao_id
naoPort => nao_port

They should be made backward compatible. A deprecation warning should also be raised.

Memory callbacks appear to be broken

At least this seems to be the case on Fedora18 when the broker is started using an IP address (if I don't use an IP address my robot is not located).

I can reproduce this in the python shell (which previously worked)

from naoutil import broker
myBroker = broker.Broker("testy", nao_id="192.168.0.71", nao_port=9559)
def my_event_callback(data_name, value, message):
print 'Event', data_name, value, message
memory.subscribe_to_event('RightBumperPressed', my_event_callback)

Exception when run on windows 7

Looks like missing dependences for avahi

Traceback (most recent call last):
File "src\main\python\recorder\main.py", line 30, in
from core import Robot, get_joints_for_chain, is_joint, get_sub_chains
File "C:\Users\dns\dev\nao\nao-recorder\src\main\python\recorder\core.py", li
ne 12, in
from naoutil import broker
File "C:\Users\dns\dev\nao\nao-recorder\src\main\python\naoutil\broker.py", l
ine 14, in
from naoutil import avahi
File "C:\Users\dns\dev\nao\nao-recorder\src\main\python\naoutil\avahi.py", li
ne 11, in
import dbus, gobject
ImportError: No module named dbus

Memory Callbacks

I was trying to use the memory callbacks and even with the README.md example you have:

 def myEventCallback(dataName, value, message):
      nao.naoscript.get(32)
 memory.subscribeToEvent('RightBumperPressed', myEventCallback)
 raw_input("Press ENTER to stop subscribing to RightBumperPressed\n")
 memory.unsubscribeToEvent('RightBumperPressed')

I get this error:

memory.subscribeToEvent('RightBumperPressed', myEventCallback)
Exception TypeError: "in method 'module_getName', argument 1 of type 'AL::module _'" in <bound method _SubscriberModule.del of <naoutil.memory._SubscriberModule; >> ignored
Traceback (most recent call last):
File "", line 1, in
File "/home/dnajd/development/personal/FluentNao/src/main/python/naoutil/memory.py", line 58, in subscribeToEvent
_SubscriberModule().subscribeToEvent(dataName, callback)
File "/home/dnajd/development/personal/FluentNao/src/main/python/naoutil/memory.py", line 20, in getinstance
instance = cls() # Keep a strong ref until it is returned
File "/home/dnajd/development/personal/FluentNao/src/main/python/naoutil/memory.py", line 28, in init
ALModule.init(self)
File "/home/dnajd/development/personal/FluentNao/src/main/python/naoutil/init.py", line 31, in init
_ALModule.init(self, self.moduleName)
File "/home/dnajd/development/opensource/pynaoqi-python-2.7-naoqi-1.14-linux64/naoqi.py", line 133, in init
inaoqi.module.init(self, param)
File "/home/dnajd/development/opensource/pynaoqi-python-2.7-naoqi-1.14-linux64/inaoqi.py", line 217, in init
this = _inaoqi.new_module(_args)
RuntimeError: module::module
Could not create a module, as there is no current broker in Python's world.

Have an environment variable to specify a prefered robot when finding a NAO

When finding a NAO (broker creation and (future) functions to find a nao around), support the use of an environment variable to suggest a preferred robot.

When no nao_id argument is given when calling the function resolving nao ip/port, the environment variable will be looked up before suggesting any default (local robot, first in list, nao.local).

Run pylint on code

Or better add script to allow run of pylint to report on any code problems

Move data dir outside behaviour dir

Choreographe will delete "unknown" directories inside the behaviour, so the default data dir location should be outside to protect it from choreographe.

NaoEnvironment should choose a default name and allow clients to override it.

Improve logging

Get access to the logging attributes in choreographe boxes so allow debug, warn etc logging calls

Everything working well except audioPlayer

I am using the env from naoutil and everything works nicely except when I try to use the audio player. I get this error:

type object 'ALProxy' has no attribute 'audioPlayer'

Add support for location detection

It would be useful for NAO to be able to recognise locations. One mechanism for doing this could be the use of wifi access points and signal strength. If a GPS sensor is attached then that could be used too.

naoutil should provide an interface to name & recognise locations that abstracts the technology used.

Better test infrastructure

Work on a test infrastructure allowing to do unit testing with fake ALModules, or using modules from a local naoqi, or from virtual machine naoqi, or from a simulation naoqi.

Support reading config items from file

NaoEnvironment makes it easier to use the i18n to localize text but it does not help with reading non-localized files. jprops & i18n provide all the necessary code to read JSON and properties files but a convenient method to read non-localized properties is required.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.