Nottinghack Creepy Urchin
  • Python 96.1%
  • Shell 3.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-16 16:09:02 +01:00
dorcas Update alien serve offsets since replacement 2026-08-16 16:08:40 +01:00
systemd Add systemd service; update docs; utility scripts. 2026-03-24 10:41:31 +00:00
util Add systemd service; update docs; utility scripts. 2026-03-24 10:41:31 +00:00
.gitignore implement action language! 2024-06-08 23:40:25 +01:00
LICENSE.md Clean repo for publishing. 2024-05-31 19:13:12 +01:00
pyproject.toml tweaks for installer 2026-04-05 18:43:22 +01:00
README.rst Updated TODOs 2026-08-16 16:09:02 +01:00
run Add systemd service; update docs; utility scripts. 2026-03-24 10:41:31 +00:00
speak Minor cosmetic edits 2026-03-24 10:12:37 +00:00

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

In Acts 8 verses 36-39, some woman called Dorcas falls ill and dies, and
then rises again. No worshippers for her, just more thankless life.
Thanks God! Anyhow, Dorcas is the name of the second incarnation of this
stupid talking Creepy Urchin thing at Nottingham Hackspace.

Some time in 2024 it fell ill and died (just like Dorcas - it must be
prophesy!). Probably what happened is that it was unplugged or there was
a power outage, and then either nobody could figure out how to (or
didn’t want to) turn on the crotch-speaker.

Subsequently, the speaker was removed and various random wires and
crappy speakers were shoved into the chest cavity in the vain hope it
would start working again. The SD card also had bad sectors and the Pi
was starting to boot unreliably. The LCD screen had long since been
removed by some fiendish parts pilferer.

Poor little Dorcas needs a refit, so here we are.

Version 2.0 of UrchinWare(tm) has some minor changes:

Full re-write - the old code was horrible (Jeeer!):

-  Putting data in Sqlite3 rather than hard-coding everything (wowsers!)
-  Splitting modules into separate files (zammo!)
-  Stripping out the display parts since there is no display (hurrah!)
-  Simpler dependencies (no more Qt or need for a display server -
   huzzah!)
-  New features! (zaaaaap!)


Building & Running
==================

1. Install your OS on the device where you want to run this project. The
   Nottinghack Urchin runs on a RPi 3. At various times the base OS has
   been Raspbian (lite image) and OpenEuler. Some extra packages must be
   added over a base install (Raspbian package names here):

   -  ``sox``
   -  ``espeak``
   -  ``espeak-ng``
   -  ``bind9-dnsutils``
   -  ``sqlite3``
   -  ``pigpiod``
   -  ``scanlogd`` (optional - if you want to be informed of portscans)
   -  ``mosquitto`` (optional - if you want to run a local MQTT server
      for testing)

   Run: `sudo systemctl enable pigpiod ; sudo systemctl start pigpiod`

2. Clone project; create a virtual env and install Python dependencies:

::

       # mkdir /opt
       # git clone https://github.com/mousefad/urchinware-2.git /opt/urchin
       # cd /opt/urchin
       # python -m venv .venv
       # . .venv/bin/activate
       # pip install --upgrade pip
       # pip install .

3. Set a couple of environment variables so the program can find
   relevant files:

::

       $ export DORCAS_DATABASE=/opt/urchin/db.sqlite3
       $ export DORCAS_AUDIO_DIRS=/opt/urchin/audio

4. Generate a config database from the sample, ``dorcas/sample/db.xml``
   (this file is a *GNUmeric* spreadsheet for easy viewing/editing).
   This command will generate the file at the path defined by
   ``DORCAS_DATABASE``.

::

       # python dorcas/database.py load dorcas/sample/db.xml 

5. Run with ``python -m dorcas --config default``


Configuration
=============

The database can store multiple configurations - one per record in the
``CONFIGS`` table. Only one configuration can be active when the program
is running. The configuration to use can be selected using the
``--config <id>`` option when starting the program. If the ``--config``
option is not used, the program will select the record where the ``ID``
field matches the hostname of the machine where the program is running.

A configuration selects a record from the ``BROKERS`` table to decide
which MQTT broker to connect to. At time of writing broker certificates
are not supported (since the one at Nottingham hackspace is open /
unencrypted).

Other tables:

-  ``VOICES`` - parameters for the ``espeak`` text-to-speech engine.
-  ``EFFECTS`` - post-procesing of speech audio with ``play`` (part of
   SOX).
-  ``IGNORES`` - patterns of MQTT messages to ignore.
-  ``SPECIAL_DAYS`` - define holidays and such.
-  ``GREETINGS`` - generic and personalized greetings.
-  ``MUSINGS`` - responses to general MQTT events and interctions with
   DonationBot.

Editing Config With GNUmeric
----------------------------

The sample config database is provided as a *GNUmeric* spreadsheet in
XML format, which is convenient to read and edit using the GNUmeric Free
software application. This can then be converted to an SQLite database
withthe following command (from the repo root directory):

::

   # python dorcas/database.py load path/to/spreadsheet.xml

The spreadsheet will be converted into an SQLite database which will be
saved to the path defined by the ``DORCAS_DATABASE`` environment
variable.

Editing the spreadsheet and re-generating the SQLite database is a
fairly convenient way to work, but you do you - edit that database
however you like.

Greetings
---------

Greetings are triggered when someone enters the front door of the
hackspace (i.e. the door is opened from the outside with an RFID card).

One Greeting will be selected and the Action performed. Greetings are
selected from the ``GREETINGS`` table in the database as follows:

1. Select where
   ``GREETINGS.MEMBER is NULL or GREETINGS.MEMBER == <member_name>``
2. Drop records where ``GREETINGS.CONDITION`` is not ``NULL`` or the
   condition evaluates to ``False`` (see *Conditions*, below).
3. From the remaining records select one greeting randomly (weighted by
   the ``WEIGHT`` value).
4. Perform the ACTION (see *Actions*, below).

Musings
-------

Musings are triggered by sensations. Sensations occur when any MQTT
message is seen (with the topic and message from the MQTT message. Some
sensations are generated internally (e.g. when Urchin starts up or shuts
down, or when Urchin becomes bored).

Every time a sensation is experienced by the Brain, records in the
``MUSINGS`` database table are evaluated as followed:

1. Select all records where ``MUSINGS.TOPIC`` matches the ``topic`` of
   the Sensation.
2. Drop records where ``MUSINGS.CONDITION`` is not ``NULL`` or the
   condition evaulates to ``False`` (see *Conditions*, below).
3. From the remaining records select one musing randomly (weighted by
   the ``WEIGHT`` value).
4. Perform the ACTION (see *Actions*, below).

Conditions
----------

Greetings and Musings are filtered by Conditions. A condition is a
Python expression that evaluates to True or False. Conditions are
evaulated with a limited execution context with access only to Brain
State Variables and the following functions:

-  ``random()`` Call Python ``random.random()``.

-  ``random_choice(list)`` Call Python ``random.choice(list)``

-  ``random_int(a, b)`` Call Python ``random.randint(a, b)``.

Actions
-------

Actions are small Python programs that are performed as a consequency of
a Greeting or Musing. Actions execute in a limited execution context
with access only to Brain State Variables and the following functions:

-  ``eyes(final, duration=0.5)`` Fade Urchin’s eyes to intensity level
   ``final`` (value between 0.0 and 1.0), taking ``duration`` number of
   seconds to do so. This operation is non-blocking.

-  ``log(message)`` Print ``message`` to diagnostic output with level
   ``logging.INFO``.

-  ``pause(seconds)`` Block execution of the action for the specified
   number of seconds.

-  ``play(file_id, bg=True, volume=1.0)`` Play an audio file, found
   under any of the directories defined in the environment variable
   ``DORCAS_AUDIO_DIRS`` (separated by the ``:`` character). ``volume``
   is a floating point value between 0.0 and 1.0. The ``bg`` value
   determines if the play operation blocks or if playing of the audio
   file is performed in the background.

-  ``publish(topic, message)`` Publish a message over MQTT with the
   specified topic and message payload.

-  ``random()`` Call Python ``random.random()``.

-  ``random_choice(list)`` Call Python ``random.choice(list)``

-  ``random_int(a, b)`` Call Python ``random.randint(a, b)``.

-  ``say(text, voice="default")`` cause Urchin to say ``text`` using
   voice from ``VOICES`` table where ``ID`` == ``voice``.

Brain State Variables
---------------------

Dorcas’ mind holds some state that is visible to most classes, and can
be used in Actions and Conditions.


Acknowldgements & Copyright Notices
===================================

``dorcas/sample/audio/cuckoo_chime.wav``
----------------------------------------

Is cut from ``Cuckoo Clock-Half hour.wav`` by *lonemonk* –
https://freesound.org/s/88725/ License: Attribution 3.0

``dorcas/sample/audio/announcement_chimes.wav``
-----------------------------------------------

``Tannoy chime 02.wav`` by *kwahmah_02* https://freesound.org/s/245953/
License: Attribution 3.0


TODO
====

-  MQTT re-connection
-  Greetings for people with bookings on tools, e.g. “your booking on
   the starts in 25 minutes.”
- sunrise & sunset events
- night musics