|
| 1 | +Probes |
| 2 | +==== |
| 3 | + |
| 4 | +The [last post][] introduced [Atom][atom-hackage] with a simple example that I used to cover the basics. I'm going to extend this to cover [probes][hackage-probe]. Probes are a bit vague. The only explicit information I can find is: |
| 5 | + |
| 6 | +- A [StackOverflow][stackoverflow-haskell] reply from [Tom Hawkins][hawkins] - Atom's author, incidentally. |
| 7 | +- The Atom source code, [here](http://hackage.haskell.org/package/atom-1.0.12/docs/src/Language-Atom-Language.html#probe) and [here](http://hackage.haskell.org/package/atom-1.0.12/docs/src/Language-Atom-Elaboration.html#elaborate). |
| 8 | + |
| 9 | +Here is my own working definition after some examples: *A probe allows inspecting any expression in Atom, remotely to its context, and at any desired rate.* |
| 10 | + |
| 11 | +The type signature suggests that inserting a probe is a matter of inserting [probe][hackage-probe] anywhere inside of an Atom specification, along with a probe name and an expression, and that accessing probes is a matter of examining [probes][hackage-probes]. |
| 12 | + |
| 13 | +The preamble is identical to the prior example: |
| 14 | + |
| 15 | +> module Main where |
| 16 | +> |
| 17 | +> import Language.Atom |
| 18 | +> import Language.Atom.Unit |
| 19 | +> import GHC.Word |
| 20 | +> import GHC.Int |
| 21 | +> |
| 22 | +> main :: IO () |
| 23 | +> main = do |
| 24 | +> (sched, _, _, _, _) <- compile "atom_example2" atomCfg example |
| 25 | +> putStrLn $ reportSchedule sched |
| 26 | +> |
| 27 | +> atomCfg :: Config |
| 28 | +> atomCfg = defaults { cFuncName = "atom_tick" |
| 29 | +> , cStateName = "state_example" |
| 30 | +> , cCode = prePostCode |
| 31 | +> , hCode = prePostHeader |
| 32 | +> , cRuleCoverage = False |
| 33 | +> } |
| 34 | + |
| 35 | +In the last post I ignored the arguments to the functions set in `cCode` and `hCode` in [Config](http://hackage.haskell.org/package/atom-1.0.12/docs/Language-Atom-Code.html#t:Config). The third argument is a list of probes along with their types. |
| 36 | + |
| 37 | +The below code is identical, save for three changes: |
| 38 | + |
| 39 | +- The definition of `probeStr` which turns a probe name & type into a String. |
| 40 | +- Assigning a name `probeList` to the `[(Name,Type)]` argument in `prePostCode`. |
| 41 | +- Appending `map probeStr probeList` to the list. This serves no functional purpose, it just adds comments into the code to illustrate what probes are present. |
| 42 | + |
| 43 | +> probeStr :: (Name, Type) -> String |
| 44 | +> probeStr (n, t) = "// Probe: " ++ n ++ ", type: " ++ show t |
| 45 | +> |
| 46 | +> prePostCode :: [Name] -> [Name] -> [(Name, Type)] -> (String, String) |
| 47 | +> prePostCode _ _ probeList = |
| 48 | +> ( unlines $ [ "// ---- This source is automatically generated by Atom ----" |
| 49 | +> , "#include <stdlib.h>" |
| 50 | +> , "#include <stdio.h>" |
| 51 | +> , "#include <unistd.h>" |
| 52 | +> , "" |
| 53 | +> , "bool g_sensor_ready;" |
| 54 | +> , "uint16_t g_sensor_value;" |
| 55 | +> , "void sensor_on(void);" |
| 56 | +> , "void sensor_off(void);" |
| 57 | +> , "void sensor_trigger(void);" |
| 58 | +> , "" |
| 59 | +> ] ++ map probeStr probeList |
| 60 | +> , unlines [ "int main(void) {" |
| 61 | +> , " while (true) {" |
| 62 | +> , " atom_tick();" |
| 63 | +> , " usleep(1000);" |
| 64 | +> , " }" |
| 65 | +> , " return 0;" |
| 66 | +> , "}" |
| 67 | +> , "void sensor_on(void) {" |
| 68 | +> , " printf(\"%lu: sensor_on()\\n\", __global_clock);" |
| 69 | +> , "}" |
| 70 | +> , "" |
| 71 | +> , "void sensor_off(void) {" |
| 72 | +> , " printf(\"%lu: sensor_off()\\n\", __global_clock);" |
| 73 | +> , "}" |
| 74 | +> , "" |
| 75 | +> , "void sensor_trigger(void) {" |
| 76 | +> , " if (rand() % 4) {" |
| 77 | +> , " g_sensor_value = rand();" |
| 78 | +> , " g_sensor_ready = true;" |
| 79 | +> , " printf(\"%lu: Triggered sensor, value=%u\\n\"," |
| 80 | +> , " __global_clock, g_sensor_value);" |
| 81 | +> , " }" |
| 82 | +> , "}" |
| 83 | +> , "" |
| 84 | +> , "// ---- End automatically-generated source ----" |
| 85 | +> ]) |
| 86 | +> |
| 87 | +> prePostHeader :: [Name] -> [Name] -> [(Name, Type)] -> (String, String) |
| 88 | +> prePostHeader _ _ _ = |
| 89 | +> ( unlines [ "// ---- This header is automatically generated by Atom ----" |
| 90 | +> ] |
| 91 | +> , unlines [ "// ---- End automatically-generated header ----" |
| 92 | +> ]) |
| 93 | + |
| 94 | + |
| 95 | +I reordered things from the last example to give some simpler definitions first. First I add a probe into `tickSecond` for the value of `clock` (which thus far I have not done anything with): |
| 96 | + |
| 97 | +> tickSecond :: Atom (V Word64) |
| 98 | +> tickSecond = do |
| 99 | +> clock <- word64 "clock_sec" 0 |
| 100 | +> probe "Clock" $ value clock |
| 101 | +> period 1000 $ exactPhase 0 $ atom "second" $ incr clock |
| 102 | +> return clock |
| 103 | + |
| 104 | +And a probe into `checkSensor` for the value of `sensorValue`: |
| 105 | + |
| 106 | +> checkSensor :: Word16 -> Atom () -> Atom () |
| 107 | +> checkSensor threshold overThresholdAction = atom "check_sensor" $ do |
| 108 | +> ready <- return $ bool' "g_sensor_ready" |
| 109 | +> sensorValue <- return $ word16' "g_sensor_value" |
| 110 | +> warmup <- timer "warmup" |
| 111 | +> triggered <- bool "triggered" False |
| 112 | +> sensorOn <- bool "sensor_on" False |
| 113 | +> |
| 114 | +> probe "Sensor Value" $ value sensorValue |
| 115 | +> |
| 116 | +> period 2000 $ phase 500 $ atom "powerOn" $ do |
| 117 | +> call "sensor_on" |
| 118 | +> triggered <== false |
| 119 | +> ready <== false |
| 120 | +> sensorOn <== true |
| 121 | +> startTimer warmup $ Const 10 |
| 122 | +> |
| 123 | +> atom "trigger" $ do |
| 124 | +> cond $ timerDone warmup &&. not_ (value triggered) &&. value sensorOn |
| 125 | +> triggered <== true |
| 126 | +> call "sensor_trigger" |
| 127 | +> |
| 128 | +> atom "checkSensorValue" $ do |
| 129 | +> cond $ value ready |
| 130 | +> ready <== false |
| 131 | +> sensorOn <== false |
| 132 | +> call "sensor_off" |
| 133 | +> atom "checkThreshold" $ do |
| 134 | +> cond $ value sensorValue >. Const threshold |
| 135 | +> overThresholdAction |
| 136 | +> |
| 137 | +> period 2000 $ phase 550 $ atom "powerOff" $ do |
| 138 | +> cond $ value sensorOn |
| 139 | +> ready <== false |
| 140 | +> printStrLn "Sensor timeout." |
| 141 | +> call "sensor_off" |
| 142 | + |
| 143 | +I can access all probes via [probes][hackage-probes], but given its type signature, some conversion is necessary: |
| 144 | +```haskell |
| 145 | +probes :: Atom [(String, UE)] |
| 146 | +``` |
| 147 | +
|
| 148 | +Primarily, that `UE` ('untyped expression') needs to be turned to something else to be useful. Luckily, [typeOf](http://hackage.haskell.org/package/atom-1.0.12/docs/Language-Atom-Expressions.html#v:typeOf) gives its type as a [Language.Atom.Expressions.Type](http://hackage.haskell.org/package/atom-1.0.12/docs/Language-Atom-Expressions.html#t:Type), and then the [Retype](http://hackage.haskell.org/package/atom-1.0.12/docs/Language-Atom-Expressions.html#v:Retype) constructor can convert it accordingly. |
| 149 | +
|
| 150 | +I'm not sure what else I can easily tell Atom to do with an expression, so I just pass it to [printIntegralE](http://hackage.haskell.org/package/atom-1.0.12/docs/Language-Atom-Unit.html#v:printIntegralE). I'm sure a suitably motivated individual could make more interesting functionality. I come up with this slightly hackish Haskell function for printing a probe's value: |
| 151 | +
|
| 152 | +*(Do you have a cleaner version? Show me.)* |
| 153 | +
|
| 154 | +> printProbe :: (String, UE) -> Atom () |
| 155 | +> printProbe (str, ue) = case typeOf ue of |
| 156 | +> Int8 -> ps (ru :: E Int8) |
| 157 | +> Int16 -> ps (ru :: E Int16) |
| 158 | +> Int32 -> ps (ru :: E Int32) |
| 159 | +> Int64 -> ps (ru :: E Int64) |
| 160 | +> Word8 -> ps (ru :: E Word8) |
| 161 | +> Word16 -> ps (ru :: E Word16) |
| 162 | +> Word32 -> ps (ru :: E Word32) |
| 163 | +> Word64 -> ps (ru :: E Word64) |
| 164 | +> where ps :: IntegralE a => E a -> Atom () |
| 165 | +> ps = printIntegralE str |
| 166 | +> ru :: IntegralE a => E a |
| 167 | +> ru = Retype ue |
| 168 | +
|
| 169 | +Finally, in `example` I add in a rule `monitor` to print all the probe values. For sanity's sake, the period is 100 for a rate of every 1/10 second rather than at the base rate. |
| 170 | +
|
| 171 | +> example :: Atom () |
| 172 | +> example = do |
| 173 | +> |
| 174 | +> clock <- tickSecond |
| 175 | +> |
| 176 | +> checkSensor 40000 $ do |
| 177 | +> printStrLn "Sensor value over threshold!" |
| 178 | +> |
| 179 | +> period 100 $ atom "monitor" $ do |
| 180 | +> mapM_ printProbe =<< probes |
| 181 | +> |
| 182 | +
|
| 183 | +In the end the monitor C code looks like this: |
| 184 | +```c |
| 185 | +/* atom_example2.monitor */ |
| 186 | +static void __r6() { |
| 187 | + bool __0 = true; |
| 188 | + uint16_t __1 = g_sensor_value; |
| 189 | + uint64_t __2 = state_example.atom_example2.clock_sec; |
| 190 | + if (__0) { |
| 191 | + printf("Sensor Value: %i\n", __1); |
| 192 | + printf("Clock: %i\n", __2); |
| 193 | + } |
| 194 | +} |
| 195 | +``` |
| 196 | +
|
| 197 | +Fairly simple, but I can see how it would be useful. |
| 198 | +
|
| 199 | +[source code]: ./2015-02-20-atom-part-2.lhs |
| 200 | +[last post]: ./2015-02-17-atom-examples.html |
| 201 | +[atom-hackage]: http://hackage.haskell.org/package/atom "atom: A DSL for embedded hard realtime applications. (hackage)" |
| 202 | +[atom-github]: https://github.com/tomahawkins/atom "atom: A DSL for embedded hard realtime applications. (github)" |
| 203 | +[hawkins]: http://tomahawkins.org/ |
| 204 | +[stackoverflow-haskell]: https://stackoverflow.com/questions/1263711/using-haskell-for-sizable-real-time-systems-how-if#answer-1582191 "Using Haskell for sizable real-time systems: how (if?)?" |
| 205 | +[hackage-probe]: http://hackage.haskell.org/package/atom-1.0.12/docs/Language-Atom-Language.html#v:probe |
| 206 | +[hackage-probes]: http://hackage.haskell.org/package/atom-1.0.12/docs/Language-Atom-Language.html#v:probes |
0 commit comments