git-annex devblog (Joey devblog)
day 619 important bugfix release and v8 too

A nasty bug that made git-annex store content on gcrypt and git-lfs without encrypting it led to a bugfix-only release, 7.20200226.

Since v8 was already close to release -- I was thinking probably Friday -- and the autobuilders are already building that version, it made sense to move up the v8 release as well, so that's also been released today.

That bug was happened because of an oversight when I was doing the big remote config parsing change. I tested that a lot, but I didn't think to test that gcrypt actually stored content encrypted! I need to do something about network test suite, so this kind of breakage in special remotes can be caught.

git-annex devblog (Joey devblog)
day 618 v8 merged

v8 is now merged into master and so the next release will use v8.

The presumably last v7 release happened earlier today, with some accumulated changes, including a data loss bug in git-annex fsck --from remote -J.

arduino-copilot combinators

My framework for programming Arduinos in Haskell has two major improvements this week. It's feeling like I'm laying the keystone on this project. It's all about the combinators now.

Sketch combinators

Consider this arduino-copilot program, that does something unless a pause button is pushed:

paused <- input pin3
pin4 =: foo @: not paused
v <- input a1
pin5 =: bar v @: sometimes && not paused

The pause button has to be checked everywhere, and there's a risk of forgetting to check it, resulting in unexpected behavior. It would be nice to be able to factor that out somehow. Also, notice that it inputs from a1 all the time, but won't use that input when pause is pushed. It would be nice to be able to avoid that unnecessary work.

The new whenB combinator solves all of that:

paused <- input pin3
whenB (not paused) $ do
    pin4 =: foo
    v <- input a1
    pin5 =: bar v @: sometimes

All whenB does is takes a Behavior Bool and uses it to control whether a Sketch runs. It was not easy to implement, given the constraints of Copilot DSL, but it's working. And once I had whenB, I was able to leverage RebindableSyntax to allow if then else expressions to choose between Sketches, as well as between Streams.

Now it's easy to start by writing a Sketch that describes a simple behavior, like turnRight or goForward, and glue those together in a straightforward way to make a more complex Sketch, like a line-following robot:

ll <- leftLineSensed
rl <- rightLineSensed
if ll && rl
    then stop
    else if ll
        then turnLeft
        else if rl
            then turnRight
            else goForward

(Full line following robot example here)

TypedBehavior combinators

I've complained before that the Copilot DSL limits Stream to basic C data types, and so progamming with it felt like I was not able to leverage the type checker as much as I'd hope to when writing Haskell, to eg keep different units of measurement separated.

Well, I found a way around that problem. All it needed was phantom types, and some combinators to lift Copilot DSL expressions.

For example, a Sketch that controls a hot water heater certainly wants to indicate clearly that temperatures are in C not F, and PSI is another important unit. So define some empty types for those units:

data PSI
data Celsius

Using those as the phantom type parameters for TypedBehavior, some important values can be defined:

maxSafePSI :: TypedBehavior PSI Float
maxSafePSI = TypedBehavior (constant 45)

maxWaterTemp :: TypedBehavior Celsius Float
maxWaterTemp = TypedBehavior (constant 35)

And functions like this to convert raw ADC readings into our units:

adcToCelsius :: Behavior Float -> TypedBehavior Celsius Float
adcToCelsius v = TypedBehavior $ v * (constant 200 / constant 1024)

And then we can make functions that take these TypedBehaviors and run Copilot DSL expressions on the Stream contained within them, producing Behaviors suitable for being connected up to pins:

isSafePSI :: TypedBehavior PSI Float -> Behavior Bool
isSafePSI p = liftB2 (<) p maxSafePSI

isSafeTemp :: TypedBehavior Celsius Float -> Behavior Bool
isSafeTemp t = liftB2 (<) t maxSafePSI

(Full water heater example here)

BTW, did you notice the mistake on the last line of code above? No worries; the type checker will, so it will blow up at compile time, and not at runtime.

    • Couldn't match type ‘PSI’ with ‘Celsius’
      Expected type: TypedBehavior Celsius Float
        Actual type: TypedBehavior PSI Float

The liftB2 combinator was all I needed to add to support that. There's also a liftB, and there could be liftB3 etc. (Could it be generalized to a single lift function that supports multiple arities? I don't know yet.) It would be good to have more types than just phantom types; I particularly miss Maybe; but this does go a long way.

So you can have a good amount of type safety while using Copilot to program your Arduino, and you can mix both FRP style and imperative style as you like. Enjoy!

This work was sponsored by Trenton Cronholm and Jake Vosloo on Patreon.

rstidyman (Richard)
Drum Beat
rt n dd after kayaking June 2016Facebook is a funny thing. I suspect most of us are lurkers. I do my share of lurking when not in the mood to say anything. Yes, that does happen. Oftentimes though, I feel compelled to share something important. It might be:
  • a special photo with my grandchildren or Danielle Darter.
  • some general tidbit of fun, like hiking with a friend.
  • political
  • oriented towards personal growth.
Here is the observation. Cute pictures get lots of likes…so I know lots of folks see it. Fun tidbits may get 22+ likes. Political may get 8-12 likes. Personal growth. Hardly any.
with aine
I’m not invested in getting likes but I think it is interesting. I wonder if when it comes to politics, fewer people like or comment. I wonder if people don’t want to be exposed, or fear confrontation, haven’t formed an opinion or is it just apathy?
As for the personal growth stuff? Very few if any at all likes or comments. Curious and curiouser. I wonder if my attempt to toss out a few seeds is beneficial to anyone. I don’t need it to be. I’m just satisfying my need to express and create, and think and ponder and put it down. If anyone benefits, great.
If no one else benefits, fine too. It does make me wonder. Maybe I’m in the minority of folks that consider personal growth valuable? As for others? Not interested? Is it apathy? Ain’t got time? Too big and scary to explore that stuff. Maybe it’s just too highfalutin. Maybe people are concerned might stir up something? Rock the proverbial boat inside your head or your relationships? Your life?
perma 0 likes
I like exploring the thoughtful and sometimes radical side. I like rocking the boat with provocative ideas. That’s why the closest friends I have rock boats. They step away from normal and what is expected and do their own thing. They are willing to venture into the wilderness. Alone, so it seems. I consider it just a meet up of those that dance to the beat of different drums.
And yes, some say I think too much. I believe I think just the right about for me.I don’t work at it. it is just the way I am. And I like it.
Anna (Anna and Mark: Waldeneffect)
Honey locust and osage orange as firewood
Gathering firewood

Every location has a few easy tree species to harvest for firewood. Here on the ridge, we have a massive pile of lumber that was pushed aside during the construction of our septic system. And just about all of it is either honey locust or osage orange. The question became --- are either or both good for firewood?

Short version: both burn hot and well. Of course, it's more complicated than that.

Honey locust turns out to be a pretty optimal firewood (as long as you're careful not to jab yourself with the thorns). At 26.7 million BTUs per cord, it burns nearly as hot as black locust (27.9) and is much easier to split. I'm so glad to have such an excellent keep-the-fire-going wood close at hand!

Only downside? Honey locust is not a kindling wood. If this was our only wood, we'd have a bear of a time getting a fire started.

Osage orange sawdust

Unlike honey locust, osage orange is impossible to confuse with anything else. As soon as we cut into our first log, we were wowed by the yellow sawdust. Then we brought some to the chopping block and started swearing --- despite what the internet says, our osage orange was pretty difficult to split. Luckily, most of the logs were small enough they could go into the fire whole.

Inside, I soon found that osage orange is great for starting fires. Even though the logs feel heavy (and do burn extremely hot, clocking in at 32.9 BTU), they have a chemistry that makes them spark heavily. I suspect that same chemistry makes kindling light fast.

One warning: do not leave an osage-orange fire unattended unless you completely close the stove air vent because the sparks travel far and wide. On the other hand, the same feature can be very entertaining if you're sitting in front of the stove with a cat and a book listening to the snap, crackle, pop.

Overall, I'd say we got very lucky with our first round of firewood species here on the ridge. High BTUs mean much less work per unit heat. I'd say we've put about half as much effort into our fire this winter compared to what we used to do when burning tulip poplar, box elder, and black walnut down by the creek.

arduino-copilot one week along

My framework for programming Arduinos in Haskell in FRP-style is a week old, and it's grown up a lot.

It can do much more than flash a light now. The =: operator can now connect all kinds of FRP Events to all kinds of outputs. There's some type level progamming going on to only allow connections that make sense. For example, arduino-copilot knows what pins of an Adruino support DigitalIO and which support PWM. There are even nice custom type error messages:

demo.hs:7:9: error:
    • This Pin does not support digital IO
    • In a stmt of a 'do' block: a6 =: blinking

I wanted it to be easy to add support to arduino-copilot for using Arduino C libraries from Haskell, and that's proven to be the case. I added serial support last weekend, which is probably one of the harder libraries. It all fell into place once I realized it should not be about individual printfs, but about a single FRP behavior that describes all output to the serial port. This interface was the result:

n <- input a1 :: Sketch (Behavior ADC)
Serial.device =: [Serial.str "a1:", n, Serial.char '\n']
Serial.baud 9600

This weekend I've been adding support for the EEPROMex library, and the Functional Reactive Programming approach really shines in stuff like this example, which gathers data from a sensor, logs it to the serial port, and also stores every 3rd value into the EEPROM for later retrival, using the whole EEPROM as a rolling buffer.

v <- input a1 ([10, 20..] :: [ADC])
range <- EEPROM.allocRange sizeOfEEPROM :: Sketch (EEPROM.Range ADC)
range =: EEPROM.sweepRange 0 v @: frequency 3
led =: frequency 3
Serial.device =: [ v, Serial.char '\n']
Serial.baud 9600
delay =: MilliSeconds (constant 10000)

There's a fair bit of abstraction in that... Try doing that in 7 lines of C code with that level of readability. (It compiles into 120 lines of C.)

Copilot's ability to interpret the program and show what it would do, without running it on the Adruino, seems more valuable the more complicated the programs become. Here's the interpretation of the program above.

delay:     digitalWrite_13:      eeprom_range_write1:  output_Serial:       
(10000)    (13,false)            --                    (10)                 
(10000)    (13,true)             (0,20)                (20)                 
(10000)    (13,false)            --                    (30)                 
(10000)    (13,false)            --                    (40)                 
(10000)    (13,true)             (1,50)                (50)                 
(10000)    (13,false)            --                    (60)          

Last night I was writing a program that amoung other things, had an event that only happened once every 70 minutes (when the Arduino's micros clock overflows). I didn't have to wait hours staring at the Arduino to test and debug my program, instead I interpreted it with a clock input that overflowed on demand.

(Hmm, I've not actually powered my Arduino on in nearly a week despite writing new Arduino programs every day.)

So arduino-copilot is feeling like it's something that I'll be using soon to write real world Arduino programs. It's certianly is not usable for all Arduino programming, but it will support all the kinds of programs I want to write, and being able to use Functional Reactive Programming will make me want to write them.

Development of arduino-copilot was sponsored by Trenton Cronholm and Jake Vosloo on Patreon.

announcing arduino-copilot

arduino-copilot, released today, makes it easy to use Haskell to program an Arduino. It's a FRP style system, and uses the Copilot DSL to generate embedded C code.

gotta blink before you can run

To make your arduino blink its LED, you only need 4 lines of Haskell:

import Copilot.Arduino
main = arduino $ do
    led =: blinking
    delay =: constant (MilliSeconds 100)

Running that Haskell program generates an Arduino sketch in an .ino file, which can be loaded into the Arduino IDE and uploaded to the Arduino the same as any other sketch. It's also easy to use things like Arduino-Makefile to build and upload sketches generated by arduino-copilot.

shoulders of giants

Copilot is quite an impressive embedding of C in Haskell. It was developed for NASA by Galois and is intended for safety-critical applications. So it's neat to be able to repurpose it into hobbyist microcontrollers. (I do hope to get more type safety added to Copilot though, currently it seems rather easy to confuse eg miles with kilometers when using it.)

I'm not the first person to use Copilot to program an Arduino. Anthony Cowley showed how to do it in Abstractions for the Functional Roboticist back in 2013. But he had to write a skeleton of C code around the C generated by Copilot. Amoung other features, arduino-copilot automates generating that C skeleton. So you don't need to remember to enable GPIO pin 13 for output in the setup function; arduino-copilot sees you're using the LED and does that for you.

frp-arduino was a big inspiration too, especially how easy it makes it to generate an Arduino sketch withough writing any C. The "=:" operator in copilot-arduino is copied from it. But ftp-arduino contains its own DSL, which seems less capable than Copilot. And when I looked at using frp-arduino for some real world sensing and control, it didn't seem to be possible to integrate it with existing Arduino libraries written in C. While I've not done that with arduino-copilot yet, I did design it so it should be reasonably easy to integrate it with any Arduino library.

a more interesting example

Let's do something more interesting than flashing a LED. We'll assume pin 12 of an Arduino Uno is connected to a push button. When the button is pressed, the LED should stay lit. Otherwise, flash the LED, starting out flashing it fast, but flashing slower and slower over time, and then back to fast flashing.

{-# LANGUAGE RebindableSyntax #-}
import Copilot.Arduino.Uno

main :: IO ()
main = arduino $ do
        buttonpressed <- input pin12
        led =: buttonpressed || blinking
        delay =: MilliSeconds (longer_and_longer * 2)

This is starting to use features of the Copilot DSL; "buttonpressed || blinking" combines two FRP streams together, and "longer_and_longer * 2" does math on a stream. What a concise and readable implementation of this Arduino's behavior!

Finishing up the demo program is the implementation of longer_and_longer. This part is entirely in the Copilot DSL, and actually I lifted it from some Copilot example code. It gives a reasonable flavor of what it's like to construct streams in Copilot.

longer_and_longer :: Stream Int16
longer_and_longer = counter true $ counter true false `mod` 64 == 0

counter :: Stream Bool -> Stream Bool -> Stream Int16
counter inc reset = cnt
        cnt = if reset then 0 else if inc then z + 1 else z
        z = [0] ++ cnt

This whole example turns into just 63 lines of C code, which compiles to a 1248 byte binary, so there's plenty of room left for larger, more complex programs.

simulating an Arduino

One of Copilot's features is it can interpret code, without needing to run it on the target platform. So the Arduino's behavior can be simulated, without ever generating C code, right at the console!

But first, one line of code needs to be changed, to provide some button states for the simulation:

        buttonpressed <- input' pin12 [False, False, False, True, True]

Now let's see what it does:

# runghc demo.hs -i 5
delay:         digitalWrite_13: 
(2)            (13,false)    
(4)            (13,true)     
(8)            (13,false)    
(16)           (13,true)     
(32)           (13,true)     

Which is exactly what I described it doing! To prove that it always behaves correctly, you could use copilot-theorem.

peek at C

Let's look at the C code that is generated by the first example, of blinking the LED.

This is not the generated code, but a representation of how the C compiler sees it, after constant folding, and some very basic optimisation. This compiles to the same binary as the generated code.

void setup() {
      pinMode(13, OUTPUT);
void loop(void) {
      digitalWrite(13, s0[s0_idx]);
      s0_idx = (++s0_idx) % 2;

If you compare this with hand-written C code to do the same thing, this is pretty much optimal!

Looking at the C code generated for the more complex example above, you'll see few unnecessary double computations. That's all I've found to complain about with the generated code. And no matter what you do, Copilot will always generate code that runs in constant space, and constant time.

Development of arduino-copilot was sponsored by Trenton Cronholm and Jake Vosloo on Patreon.


List of feeds:

  • Anna: last checked (50 posts)
  • Anna and Mark: Waldeneffect: last checked (4544 posts)
  • Joey: last checked (201 posts)
  • Joey devblog: last checked (243 posts)
  • Jay: last checked (50 posts)
  • Errol: last checked (53 posts)
  • Maggie: Cannot detect feed type (35 posts)
  • Maggie too: last checked (72 posts)
  • Maggie also: Not Found (437 posts)
  • Tomoko: last checked (77 posts)
  • Jerry: last checked (28 posts)
  • Dani: last checked (22 posts)
  • Richard: last checked (53 posts)