A Domain-specific toy language for controlling 26c3 DDC blinkenlights

ben / tech / 26c3-ddc


26c3 (the 26th Chaos Communications Congress, in Berlin) featured three programmable large flashing C letters outside the front entrance - DieDreiC.

Light patterns could be uploaded by anyone with a web browser in a low level language which directly controlled individual bulbs. Like several others, I felt the urge to write a higher level language to generate lower level code. What follows is a brief description of that language.

One goal was to make use of Haskell's strong typing. The low level languge has many untyped integer fields and I found when I was experimenting that I got confused about these a bit.


data LampID = SpecificLamp BulbID | AllLamp | Random | SpecificLetter Letter

LampID identifies a lamp or set of lamps - the various selections here map to the options available in the underlying language. Random in this case means random-at-the-time-of-pattern-execution, not at the time of executing the Haskell code (unlike other random behaviour in this high level language).

data Letter = LeftLetter | MiddleLetter | RightLetter
instance Random Letter

Letter refers to one of the three full C letters. It is an instance of Random so that one full letter can be chosen randomly.

data BulbID = BulbID Char deriving Show

BulbID refers to a specific light bulb. These are identified by a letter A-O. At the moment there is no checking that you only construct BulbIDs with those letters, so this is something that future development should fix.

data Color = Color Int Int Int 
instance Random Color 

black = Color 0 0 0
red = Color 255 0 0
green = Color 0 255 0
yellow = Color 255 255 0
blue = Color 0 0 255
magenta = Color 255 0 255
cyan = Color 0 255 255
white = Color 255 255 255

Color represents an RGB colour, with components between 0 and 255. As with BulbID, nothing enforces that range on the constructor, so its possible to construct invalid colours. This should be fixed. The Random type class is implemented so that random colours can be chosen (at haskell-script-execution time). The 8 main colours are defined as constants for easy use in patterns.


Commands live in the DDC monad, and are run with the runDDC IO action.

This monad makes available basic commands that correspond closely with the low level language, as well as a random number generator and a number of library routines.

A non-exhaustive selection of these:

wait n

waits for n milliseconds

colorLetter letter color

colours the specified letter with the specified colour

colorEverything color

colours all three letters with the specified colour

blankEverything = colorEverything black

blanks the whole display

An example program

Here is a complete program which flashes the first letter red, then the second letter green, then the third letter blue:

import DDC

main = runDDC $ do
        wait 1000
        illuminateLetter LeftLetter red
        illuminateLetter MiddleLetter green
        illuminateLetter RightLetter blue
        wait 5000

illuminateLetter letter color = do
  colorLetter letter color
  wait 5000
  colorLetter letter black

The example program can be compiled and run like this, generating on stdout the low level code to paste into the DDC web interface:

$ ghc --make RGB-seq.hs
[1 of 2] Compiling DDC              ( DDC.hs, DDC.o )
[2 of 2] Compiling Main             ( RGB-seq.hs, RGB-seq.o )
Linking RGB-seq ...

$ ./RGB-seq 
C ALL #000000
W 1000
C 1 #FF0000

Using the random number generator

The DDC monad provides access to the Haskell random number generator. In addition to generating random values of standard Haskell types, DDC types Letter and Color can be generated too.

The randomDDC command returns a random value, and the randomRangeDDC command returns a random value within a specified range. The type of the random value is determined by Haskell type inference so often it is unnecessary to specify which type is required.

Here is an example program which makes 19 flashes of random letters in random colours, with a random delay between flashes. Note how the types for the random letter and colour are not specified but are determined by their use later on with the colorLetter command.

import DDC
import Control.Monad

main = runDDC anim

anim = do
  replicateM_ 19 randomFlash

randomFlash = do
  letter <- randomDDC
  color <- randomDDC
  colorLetter letter color
  wait 100 
  t <- randomRangeDDC (800,1500)
  wait t


Tarball of git repository: 26c3-ddc-0.1.tar.gz (3mb)