├── .editorconfig
├── .gitattributes
├── .gitignore
├── LICENSE.md
├── README.md
├── THEME.md
├── arduino
├── Cereal
│ ├── Cereal.h
│ ├── CerealStream.cpp
│ └── CerealStream.h
└── README.md
├── babel.config.json
├── images
├── demo.gif
└── example.png
├── package-lock.json
├── package.json
├── src
├── app.js
├── components
│ ├── base
│ │ ├── box.js
│ │ ├── heading.js
│ │ └── text.js
│ ├── header.js
│ ├── header
│ │ └── status.js
│ ├── input.js
│ ├── monitor.js
│ ├── monitor
│ │ ├── direction.js
│ │ └── timestamp.js
│ ├── variables.js
│ └── variables
│ │ ├── base
│ │ ├── gauge.js
│ │ ├── indicator.js
│ │ ├── name.js
│ │ └── value.js
│ │ ├── flag.js
│ │ ├── range.js
│ │ └── variable.js
├── index.js
├── redux
│ ├── actions
│ │ ├── config.js
│ │ ├── messages.js
│ │ ├── serial.js
│ │ ├── types.js
│ │ └── variables.js
│ ├── reducers
│ │ ├── config.js
│ │ ├── index.js
│ │ ├── messages.js
│ │ ├── serial.js
│ │ └── variables.js
│ └── store
│ │ └── index.js
├── serial
│ ├── index.js
│ └── parser.js
├── theme
│ ├── index.js
│ └── presets
│ │ ├── black.js
│ │ ├── dark.js
│ │ ├── light.js
│ │ └── white.js
└── utils
│ ├── cli.js
│ ├── config.js
│ └── use-init.js
└── test
└── test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 | indent_size = 2
10 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
3 | .DS_Store
4 | .idea/
5 |
6 | /lib/
7 | *.log
8 |
9 | cereal.config.json
10 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Mladen Ilic
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Arduino Cereal Monitor
2 |
3 | Drop-in replacement for Arduino's serial monitor with variable watches.
4 |
5 | 
6 |
7 | ### How it works
8 |
9 | Use `Serial.print`, `Serial.println`, etc. to print data to **[Monitor]** window. To track variables in **[Variables]** window, use *Cereal library* ([documentation & demo](./arduino/README.md)).
10 |
11 | ### Install
12 |
13 | ```bash
14 | $ npm install --global arduino-cereal
15 | ```
16 |
17 | OR run it with [npx](https://www.npmjs.com/package/npx)
18 |
19 | ```bash
20 | $ npx arduino-cereal
21 | ```
22 |
23 | *Requires Node v14.0.0 or later*
24 |
25 | ### CLI options
26 |
27 | ```bash
28 | $ arduino-cereal --help
29 |
30 | Usage
31 | $ arduino-cereal
32 |
33 | Options
34 | --port Serial port
35 | --baud Baud rate (default: 9600)
36 | --theme Theme name (light, dark, black, white)
37 | --fps Screen render rate (default: 15)
38 | ```
39 |
40 | * **port:** – Serial port to which Arduino board is connected.
41 | * **baud:** – Baud rate. Default value 9600
42 | * **theme:** – Change the color theme. Available options are *light*, *dark*, *black*, *black*. The default theme is *dark*. Read more about [theme customization](THEME.md).
43 | * **fps** – Control the rate at which the CLI screen is updated. Helps to deal with flickering on some terminal apps (iTerm). Default: 15
44 |
45 | Example:
46 | ```bash
47 | $ arduino-cereal --port=/dev/tty.usbserial-1410 --baud=19200 --theme=light --fps=10
48 | ```
49 |
50 | ### Configuration file
51 |
52 | Alternatively, CLI options can be read from a file – `cereal.config.json`. This file is expected to be located in the current working directory.
53 |
54 | ```json5
55 | {
56 | "port": "/dev/tty.usbserial-1410",
57 | "baud": 9600,
58 | "fps": 15,
59 |
60 | "theme": { /** Theme customization options */ }
61 | }
62 | ```
63 |
64 | ### Contributing
65 |
66 | Contributions are welcome. 🎉
67 |
68 | ### License
69 |
70 | Licensed under the MIT license. See the [LICENSE.md](LICENSE.md) for more information.
71 |
--------------------------------------------------------------------------------
/THEME.md:
--------------------------------------------------------------------------------
1 | # Arduino Cereal Monitor Themes
2 |
3 | This document describes monitor theme customization.
4 |
5 | ### Getting Started
6 |
7 | These options are specified in the configuration file. Read [here](README.md#configuration-file) how to set up a configuration file.
8 |
9 | ### Configuration
10 |
11 | Below is a sample configuration file with all theme options. You can use it as a template to create a custom theme.
12 |
13 | ```json
14 | {
15 | "port": "/dev/tty.usbserial-1410",
16 | "baud": 9600,
17 | "fps": 15,
18 |
19 | "theme": {
20 | "modules": {
21 | "monitor": {
22 | "timestamp": true,
23 | "timestampLocale": "en-GB"
24 | },
25 |
26 | "variables": {
27 | "enabled": true,
28 | "range": {
29 | "fill": "▇",
30 | "empty": "-"
31 | },
32 |
33 | "flag": {
34 | "on": "▣",
35 | "off": "▢"
36 | }
37 | },
38 |
39 | "input": {
40 | "enabled": true,
41 | "prefix": "$>"
42 | }
43 | },
44 |
45 | "colors": {
46 | "default": "#839496",
47 |
48 | "global": {
49 | "heading": "#b58900",
50 | "border": "#268bd2"
51 | },
52 |
53 | "status": {
54 | "success": "#859900",
55 | "error": "#dc322f"
56 | },
57 |
58 | "monitor": {
59 | "timestamp": "#b58900",
60 | "message": "#839496"
61 | },
62 |
63 | "variables": {
64 | "name": "#839496",
65 | "value": "#839496",
66 |
67 | "range": {
68 | "fill": "#b58900",
69 | "empty": "#839496"
70 | },
71 | "flag": {
72 | "on": "#b58900",
73 | "off": "#b58900"
74 | }
75 | }
76 | }
77 | }
78 | }
79 |
80 | ```
81 |
--------------------------------------------------------------------------------
/arduino/Cereal/Cereal.h:
--------------------------------------------------------------------------------
1 | #ifndef _CEREAL_H_
2 | #define _CEREAL_H_
3 |
4 | #include "CerealStream.h"
5 |
6 | CerealStream Cereal(Serial);
7 |
8 | #endif _CEREAL_H_
9 |
--------------------------------------------------------------------------------
/arduino/Cereal/CerealStream.cpp:
--------------------------------------------------------------------------------
1 | #include "CerealStream.h"
2 |
3 | #define _CEREAL_PRINT_VARIABLE(name, value) start(Type::VARIABLE) + print(name) + d() + print(value) + end();
4 | #define _CEREAL_PRINT_RANGE(name, value, from, to) \
5 | start(Type::RANGE) + print(name) + d() \
6 | + print(value) + d() \
7 | + print(from) + d() \
8 | + print(to) + end(); \
9 |
10 | CerealStream::CerealStream(Print& stream): stream(stream) {}
11 |
12 | size_t CerealStream::variable(const char *name, char value) {
13 | return _CEREAL_PRINT_VARIABLE(name, value);
14 | }
15 |
16 | size_t CerealStream::variable(const char *name, unsigned char value) {
17 | return variable(name, (unsigned long) value);
18 | }
19 |
20 | size_t CerealStream::variable(const char *name, int value) {
21 | return variable(name, (long) value);
22 | }
23 |
24 | size_t CerealStream::variable(const char *name, unsigned int value) {
25 | return variable(name, (unsigned long) value);
26 | }
27 |
28 | size_t CerealStream::variable(const char *name, long value) {
29 | return _CEREAL_PRINT_VARIABLE(name, value);
30 | }
31 |
32 | size_t CerealStream::variable(const char *name, unsigned long value) {
33 | return _CEREAL_PRINT_VARIABLE(name, value);
34 | }
35 |
36 | size_t CerealStream::variable(const char *name, double value) {
37 | return _CEREAL_PRINT_VARIABLE(name, value);
38 | }
39 |
40 | size_t CerealStream::range(const char *name, char value, char from, char to) {
41 | return range(name, (long) value, (long) from, (long) to);
42 | }
43 |
44 | size_t CerealStream::range(const char *name, unsigned char value, unsigned char from, unsigned char to) {
45 | return range(name, (unsigned long) value, (unsigned long) from, (unsigned long) to);
46 | }
47 |
48 | size_t CerealStream::range(const char *name, int value, int from, int to) {
49 | return range(name, (long) value, (long) from, (long) to);
50 | }
51 |
52 | size_t CerealStream::range(const char *name, unsigned int value, unsigned int from, unsigned int to) {
53 | return range(name, (unsigned long) value, (unsigned long) from, (unsigned long) to);
54 | }
55 |
56 | size_t CerealStream::range(const char *name, long value, long from, long to) {
57 | return _CEREAL_PRINT_RANGE(name, value, from, to);
58 | }
59 |
60 | size_t CerealStream::range(const char *name, unsigned long value, unsigned long from, unsigned long to) {
61 | return _CEREAL_PRINT_RANGE(name, value, from, to);
62 | }
63 |
64 | size_t CerealStream::range(const char *name, double value, double from, double to) {
65 | return _CEREAL_PRINT_RANGE(name, value, from, to);
66 | }
67 |
68 | size_t CerealStream::flag(const char *name, unsigned int value) {
69 | return start(Type::FLAG) + print(name) + d() + print(value) + end();
70 | }
71 |
72 | size_t CerealStream::digital(const char *name, unsigned int value) {
73 | return flag(name, value);
74 | }
75 |
76 | size_t CerealStream::analog(const char *name, int value) {
77 | return range(name, (long) value, (long) 0, (long) 1023);
78 | }
79 |
--------------------------------------------------------------------------------
/arduino/Cereal/CerealStream.h:
--------------------------------------------------------------------------------
1 | #ifndef _CEREAL_STREAM_H_
2 | #define _CEREAL_STREAM_H_
3 |
4 | #include "Arduino.h"
5 |
6 | #define MESSAGE_START "\xc0"
7 | #define MESSAGE_END "\xc0"
8 | #define VARIABLE_DELIMTER "\xc1"
9 |
10 | class CerealStream: public Print {
11 | public:
12 | CerealStream(Print& stream);
13 |
14 | size_t variable(const char *name, char value);
15 | size_t variable(const char *name, unsigned char value);
16 | size_t variable(const char *name, int value);
17 | size_t variable(const char *name, unsigned int value);
18 | size_t variable(const char *name, long value);
19 | size_t variable(const char *name, unsigned long value);
20 | size_t variable(const char *name, double value);
21 |
22 | size_t range(const char *name, char value, char from, char to);
23 | size_t range(const char *name, unsigned char value, unsigned char from, unsigned char to);
24 | size_t range(const char *name, int value, int from, int to);
25 | size_t range(const char *name, unsigned int value, unsigned int from, unsigned int to);
26 | size_t range(const char *name, long value, long from, long to);
27 | size_t range(const char *name, unsigned long value, unsigned long from, unsigned long to);
28 | size_t range(const char *name, double value, double from, double to);
29 |
30 | size_t flag(const char *name, unsigned int value);
31 |
32 | size_t digital(const char *name, unsigned int value);
33 | size_t analog(const char *name, int value);
34 | private:
35 | enum Type {
36 | VARIABLE = 0x00,
37 | RANGE = 0x01,
38 | FLAG = 0x02
39 | };
40 |
41 | Print& stream;
42 |
43 | using Print::write;
44 |
45 | size_t start() { return print(MESSAGE_START); }
46 | size_t start(Type type) { return start() + t(type) + d(); }
47 | size_t end() { return print(MESSAGE_END); }
48 | size_t t(Type type) { return print(type); }
49 | size_t d() { return print(VARIABLE_DELIMTER); }
50 | size_t write(uint8_t c) { return stream.write(c); }
51 | };
52 |
53 | #endif _CEREAL_STREAM_H_
54 |
--------------------------------------------------------------------------------
/arduino/README.md:
--------------------------------------------------------------------------------
1 | # Arduino Cereal Library
2 |
3 | Arduino library for tracking variables in [Cereal monitor](../README.md).
4 |
5 | ### Install
6 |
7 | Download and manually install to your Arduino libraries. Read the "Manual Installation" section on Arduino's [library installation instruction](https://www.arduino.cc/en/Guide/Libraries) page.
8 |
9 | ### Usage
10 |
11 | Cereal monitor supports three different types of variables: *basic* variable, *range*, and a *flag* variable.
12 |
13 | For basic variable output, use:
14 | ```cpp
15 | Cereal.variable(name, value);
16 | ```
17 |
18 | For range variable outputs use:
19 | ```cpp
20 | Cereal.range(name, value, from, to);
21 | Cereal.analog(name, value);
22 | ```
23 |
24 | For flag variable output use:
25 | ```cpp
26 | Cereal.digital(name, value);
27 | ```
28 |
29 | ### Example
30 |
31 | ```cpp
32 | #include "Cereal.h"
33 |
34 | #define STATUS_PIN 3
35 | #define THROTTLE_PIN A2
36 |
37 | void setup() {
38 | // Set the baud rate for serial data transmission
39 | Serial.begin(9600);
40 |
41 | pinMode(STATUS_PIN, INPUT);
42 | pinMode(THROTTLE_PIN, INPUT);
43 |
44 | // Serial output shown in [Monitor] window
45 | Serial.println("Setup done...");
46 | }
47 |
48 | void loop() {
49 | int rnd = random(10, 20);
50 | int throttle = analogRead(THROTTLE_PIN);
51 | int status = digitalRead(STATUS_PIN);
52 |
53 | // Basic variable type output
54 | Cereal.variable("Random", rnd);
55 |
56 | // Range variable type output
57 | Cereal.analog("Throttle", throttle);
58 |
59 | // Flag variable type output
60 | Cereal.digital("Status", status);
61 | }
62 |
63 | ```
64 |
65 | Running this examples gives the following output:
66 |
67 | 
68 |
69 | ### Software Serial
70 |
71 | You can send data to multiple Cereal monitors by creating more than one `CerealStream`.
72 | `CerealStream` object take a destination stream it should write to.
73 |
74 | Destination stream is any object of class that inherits from built-in `Print` class.
75 |
76 | ```cpp
77 | #include "SoftwareSerial.h"
78 | #include "Cereal.h"
79 |
80 | int i = 0;
81 |
82 | SoftwareSerial sSerial(3, 4);
83 | CerealStream sCereal(sSerial);
84 |
85 | void setup() {
86 | Serial.begin(9600);
87 | sSerial.begin(9600);
88 | }
89 |
90 | void loop() {
91 | // Writes to HardwareSerial0
92 | Cereal.variable("I", i++);
93 |
94 | // Writes to SoftwareSerial RX(3)/TX(4)
95 | sCereal.variable("I", i++);
96 | }
97 |
98 | ```
99 |
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-react",
4 | [
5 | "@babel/preset-env",
6 | {
7 | "targets": {
8 | "node": true
9 | }
10 | }
11 | ]
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/images/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mladenilic/arduino-cereal/4f947aea161cbe469fde33abf8567f915c3c2f70/images/demo.gif
--------------------------------------------------------------------------------
/images/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mladenilic/arduino-cereal/4f947aea161cbe469fde33abf8567f915c3c2f70/images/example.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "arduino-cereal",
3 | "version": "1.1.0",
4 | "description": "Drop-in replacement for Arduino serial monitor",
5 | "license": "MIT",
6 | "homepage": "https://github.com/mladenilic/arduino-cereal#readme",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/mladenilic/arduino-cereal.git"
10 | },
11 | "bugs": {
12 | "url": "https://github.com/mladenilic/arduino-cereal/issues"
13 | },
14 | "keywords": [
15 | "arduino",
16 | "serial",
17 | "monitor"
18 | ],
19 | "author": "Mladen Ilic (https://github.com/mladenilic)",
20 | "bin": {
21 | "arduino-cereal": "lib/index.js"
22 | },
23 | "engines": {
24 | "node": ">=10"
25 | },
26 | "scripts": {
27 | "test": "xo && ava",
28 | "build": "rm -rf lib/ && babel src -d lib",
29 | "start": "babel-node ./src"
30 | },
31 | "files": [
32 | "lib"
33 | ],
34 | "dependencies": {
35 | "ink": "^3.0.8",
36 | "lodash.merge": "^4.6.2",
37 | "meow": "^8.0.0",
38 | "react": "^17.0.1",
39 | "react-redux": "^7.2.2",
40 | "redux": "^4.0.5",
41 | "serialport": "^9.0.2"
42 | },
43 | "devDependencies": {
44 | "@babel/cli": "^7.12.7",
45 | "@babel/core": "^7.12.7",
46 | "@babel/node": "^7.12.6",
47 | "@babel/preset-env": "^7.12.7",
48 | "@babel/preset-react": "^7.12.7",
49 | "@babel/register": "^7.12.1",
50 | "ava": "^3.13.0",
51 | "chalk": "^4.1.0",
52 | "eslint-config-xo-react": "^0.23.0",
53 | "eslint-plugin-react": "^7.21.5",
54 | "eslint-plugin-react-hooks": "^4.2.0",
55 | "ink-testing-library": "^2.1.0",
56 | "xo": "^0.35.0"
57 | },
58 | "ava": {
59 | "babel": true,
60 | "require": [
61 | "@babel/register"
62 | ]
63 | },
64 | "babel": {
65 | "presets": [
66 | "@babel/preset-env",
67 | "@babel/preset-react"
68 | ]
69 | },
70 | "xo": {
71 | "extends": "xo-react",
72 | "space": true,
73 | "rules": {
74 | "react/prop-types": "off",
75 | "object-curly-spacing": "off",
76 | "import/no-anonymous-default-export": "off",
77 | "default-param-last": "off",
78 | "no-unused-expressions": [
79 | "error",
80 | {
81 | "allowTernary": true
82 | }
83 | ]
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useCallback } from 'react';
2 |
3 | import { connect } from 'react-redux/lib/alternate-renderers';
4 | import { setConfig } from './redux/actions/config';
5 | import { updateVariable, outputVariables } from './redux/actions/variables';
6 | import { addMessage, outputMessages } from './redux/actions/messages';
7 | import { setSerialStatus } from './redux/actions/serial';
8 |
9 | import Serial from './serial';
10 |
11 | import Box from './components/base/box';
12 | import Text from './components/base/Text';
13 | import Header from './components/header';
14 | import Variables from './components/variables';
15 | import Monitor from './components/monitor';
16 | import Input from './components/input';
17 |
18 | import useInit from './utils/use-init';
19 |
20 | const App = ({ config, setConfig, updateVariable, outputVariables, addMessage, outputMessages, setSerialStatus }) => {
21 | const serial = useRef();
22 | const onInput = useCallback((input) => {
23 | serial.current.write(input);
24 | addMessage(`${input}\r\n`, true);
25 | });
26 |
27 | useInit(() => setConfig(config));
28 | useInit(() => setInterval(() => {
29 | outputVariables();
30 | outputMessages();
31 | }, 1000 / Math.min(Math.max(config.fps || 15, 1), 60)));
32 |
33 | useInit(() => {
34 | if (!config.port) {
35 | return;
36 | }
37 |
38 | serial.current = Serial.connect(config.port, config.baud || 9600)
39 | .on('connect', () => setSerialStatus('success'))
40 | .on('error', () => setSerialStatus('error'))
41 | .on('variable', ([type, name, value, ...options]) => updateVariable(type, name, value, options))
42 | .on('message', message => addMessage(message));
43 | });
44 |
45 | if (!config.port) {
46 | return ERROR: Please specify serial port.;
47 | }
48 |
49 | return (
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default connect(
62 | null,
63 | { setConfig, updateVariable, outputVariables, addMessage, outputMessages, setSerialStatus }
64 | )(App);
65 |
--------------------------------------------------------------------------------
/src/components/base/box.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Box as InkBox } from 'ink';
3 | import { connect } from 'react-redux/lib/alternate-renderers';
4 |
5 | const Box = ({ theme, children, boxRef, borderColor, ...rest }) => {
6 | const color = borderColor || theme?.colors?.global.border || theme?.colors?.default;
7 |
8 | return {children};
9 | };
10 |
11 | export default connect(
12 | state => ({ theme: state?.config?.theme || {} })
13 | )(Box);
14 |
--------------------------------------------------------------------------------
/src/components/base/heading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { connect } from 'react-redux/lib/alternate-renderers';
4 |
5 | import Box from './box';
6 | import Text from './text';
7 |
8 | const Heading = ({ theme, color, children }) => (
9 |
10 | [{children}]
11 |
12 | );
13 |
14 | export default connect(
15 | state => ({ theme: state?.config?.theme || {} })
16 | )(Heading);
17 |
--------------------------------------------------------------------------------
/src/components/base/text.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Text as InkText } from 'ink';
4 | import { connect } from 'react-redux/lib/alternate-renderers';
5 |
6 | const Text = ({ theme, color, children, ...rest }) => (
7 | {children}
8 | );
9 |
10 | export default connect(
11 | state => ({ theme: state?.config?.theme || {} })
12 | )(Text);
13 |
--------------------------------------------------------------------------------
/src/components/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Spacer } from 'ink';
4 |
5 | import Box from './base/box';
6 | import Text from './base/text';
7 | import Heading from './base/heading';
8 | import Status from './header/status';
9 |
10 | import { connect } from 'react-redux/lib/alternate-renderers';
11 |
12 | const Header = ({ config }) => (
13 |
14 | Arduino Cereal Monitor
15 | — Port: {config.port || '/'}, Baud rate: {config.baud}
16 |
17 |
18 |
19 | );
20 |
21 | export default connect(
22 | state => ({ config: state.config })
23 | )(Header);
24 |
--------------------------------------------------------------------------------
/src/components/header/status.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Text from '../base/text';
3 |
4 | import { connect } from 'react-redux/lib/alternate-renderers';
5 |
6 | const circle = String.fromCharCode(11044);
7 |
8 | const Status = ({ theme, serial }) => (
9 |
10 | Status:
11 | {serial.status === 'pending' ? 'Connecting...' : circle}
12 |
13 | );
14 |
15 | export default connect(
16 | state => ({
17 | theme: state?.config?.theme || {},
18 | serial: state.serial
19 | })
20 | )(Status);
21 |
--------------------------------------------------------------------------------
/src/components/input.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback } from 'react';
2 |
3 | import { useInput } from 'ink';
4 |
5 | import Box from './base/box';
6 | import Text from './base/text';
7 |
8 | import { connect } from 'react-redux/lib/alternate-renderers';
9 |
10 | const Input = ({ onInput, settings }) => {
11 | if (!settings.enabled) {
12 | return null;
13 | }
14 |
15 | const [value, setValue] = useState('');
16 | const postValue = useCallback((value) => {
17 | if (!onInput || value.length === 0) {
18 | return;
19 | }
20 |
21 | setValue('');
22 | onInput(value);
23 | }, [onInput]);
24 |
25 | useInput((input, key) => {
26 | if (key.return) {
27 | return postValue(value);
28 | }
29 |
30 | if (key.backspace || key.delete) {
31 | return setValue(value.slice(0, -1));
32 | }
33 |
34 | setValue(value + input);
35 | });
36 |
37 | return (
38 |
39 | {settings.prefix || ''} {value}
40 |
41 | );
42 | };
43 |
44 | export default connect(
45 | state => ({ settings: state.config?.theme?.modules?.input || {} })
46 | )(Input);
47 |
--------------------------------------------------------------------------------
/src/components/monitor.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react';
2 |
3 | import { Newline, Spacer, measureElement } from 'ink';
4 |
5 | import Box from './base/box';
6 | import Text from './base/text';
7 | import Heading from './base/heading';
8 | import Timestamp from './monitor/timestamp';
9 | import Direction from './monitor/direction';
10 |
11 | import { connect } from 'react-redux/lib/alternate-renderers';
12 | import { setMessageCount } from '../redux/actions/messages';
13 |
14 | const Monitor = ({ messages, colors, setMessageCount }) => {
15 | const ref = useRef();
16 |
17 | useEffect(() => {
18 | const { height } = measureElement(ref.current);
19 | setMessageCount(Math.max(height, 1));
20 | }, [setMessageCount]);
21 |
22 | return (
23 |
24 | Monitor
25 |
26 |
27 |
28 | {messages.map((message, index) => (
29 |
30 |
31 |
32 | {message.text}
33 | {index === messages.length - 1 ? '' : }
34 |
35 | ))}
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | export default connect(
43 | state => ({
44 | messages: state.messages.output,
45 | colors: state.config.theme?.colors?.monitor || {}
46 | }),
47 | { setMessageCount }
48 | )(Monitor);
49 |
--------------------------------------------------------------------------------
/src/components/monitor/direction.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Text from '../base/text';
3 | import {connect} from "react-redux/lib/alternate-renderers";
4 |
5 | const Direction = ({ sent, characters, colors }) => sent ?
6 | {characters.sent} :
7 | {characters.received} ;
8 |
9 | export default connect(
10 | state => ({
11 | characters: state.config?.theme?.modules?.monitor?.direction || {},
12 | colors: state.config.theme?.colors?.monitor?.direction || {}
13 | })
14 | )(Direction);
15 |
--------------------------------------------------------------------------------
/src/components/monitor/timestamp.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Text from '../base/text';
3 |
4 | import { connect } from 'react-redux/lib/alternate-renderers';
5 |
6 | const Timestamp = ({ monitor, colors, time, ...props }) => {
7 | if (!monitor.timestamp) {
8 | return null;
9 | }
10 |
11 | return [{time.toLocaleTimeString(monitor.timestampLocale)}];
12 | };
13 |
14 | export default connect(
15 | state => ({
16 | monitor: state.config?.theme?.modules?.monitor || {},
17 | colors: state.config.theme?.colors?.monitor || {}
18 | })
19 | )(Timestamp);
20 |
--------------------------------------------------------------------------------
/src/components/variables.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Box from './base/box';
4 | import Heading from './base/heading';
5 |
6 | import Variable from './variables/variable';
7 | import Range from './variables/range';
8 | import Flag from './variables/flag';
9 |
10 | import { connect } from 'react-redux/lib/alternate-renderers';
11 |
12 | const variableComponent = type => ({
13 | 0: Variable,
14 | 1: Range,
15 | 2: Flag
16 | }[type] || Variable);
17 |
18 | const Variables = ({ variables = {}, settings }) => {
19 | if (!settings.enabled) {
20 | return null;
21 | }
22 |
23 | return (
24 |
25 | Variables
26 |
27 | {Object.entries(variables).map(([name, variable]) => {
28 | const Component = variableComponent(variable.type);
29 |
30 | return ;
31 | })}
32 |
33 |
34 | );
35 | };
36 |
37 | export default connect(
38 | state => ({
39 | variables: state.variables.output,
40 | settings: state.config?.theme?.modules?.variables || {}
41 | })
42 | )(Variables);
43 |
--------------------------------------------------------------------------------
/src/components/variables/base/gauge.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Text from '../../base/text';
4 | import Box from '../../base/box';
5 | import { connect } from 'react-redux/lib/alternate-renderers';
6 |
7 | const clamp = (x, min, max) => Math.min(Math.max(x, min), max);
8 |
9 | const Gauge = ({ value, min, max, length = 20, characters, colors }) => {
10 | const completed = Math.round(length * value / (max - min));
11 |
12 | return (
13 |
14 | {min}|
15 | {(characters.fill || '▇').repeat(clamp(completed, 0, length))}
16 | {(characters.empty || '-').repeat(clamp(length - completed, 0, length))}
17 | |{max}
18 |
19 | );
20 | };
21 |
22 | export default connect(
23 | state => ({
24 | characters: state.config?.theme?.modules?.variables?.range || {},
25 | colors: state.config?.theme?.colors?.variables?.range || {}
26 | })
27 | )(Gauge);
28 |
29 |
--------------------------------------------------------------------------------
/src/components/variables/base/indicator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Text from '../../base/text';
4 | import { connect } from 'react-redux/lib/alternate-renderers';
5 |
6 | const Indicator = ({ value, colors, characters }) => (
7 | [{value ? characters.on : characters.off}]
8 | );
9 |
10 | export default connect(
11 | state => ({
12 | characters: state.config?.theme?.modules?.variables?.flag || {},
13 | colors: state.config?.theme?.colors?.variables?.flag || {}
14 | })
15 | )(Indicator);
16 |
17 |
--------------------------------------------------------------------------------
/src/components/variables/base/name.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Box from '../../base/box';
4 | import Text from '../../base/text';
5 | import { connect } from 'react-redux/lib/alternate-renderers';
6 |
7 | const VariableName = ({ name, theme }) => (
8 |
9 | {name}
10 | :
11 |
12 | );
13 |
14 | export default connect(
15 | state => ({ theme: state.config?.theme || {} })
16 | )(VariableName);
17 |
--------------------------------------------------------------------------------
/src/components/variables/base/value.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Text from '../../base/text';
4 | import { connect } from 'react-redux/lib/alternate-renderers';
5 |
6 | const VariableValue = ({ value, theme }) => {value};
7 |
8 | export default connect(
9 | state => ({ theme: state.config?.theme || {} })
10 | )(VariableValue);
11 |
--------------------------------------------------------------------------------
/src/components/variables/flag.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Box from '../base/box';
4 | import Indicator from './base/indicator';
5 | import VariableName from './base/name';
6 | import VariableValue from './base/value';
7 |
8 | const Flag = React.memo(({ variable }) => {
9 | const value = Boolean(Number.parseInt(variable.value, 10));
10 |
11 | return (
12 |
13 |
14 |
15 |
16 |
17 | );
18 | });
19 |
20 | export default Flag;
21 |
--------------------------------------------------------------------------------
/src/components/variables/range.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Box from '../base/box';
4 | import Text from '../base/text';
5 | import VariableName from './base/name';
6 | import VariableValue from './base/value';
7 | import Gauge from './base/gauge';
8 |
9 | const Range = React.memo(({ variable }) => {
10 | const [min, max] = variable.options.map(a => Number.parseFloat(a));
11 | const value = Number.parseFloat(variable.value);
12 |
13 | return (
14 |
15 |
16 |
17 | ()
18 |
19 | );
20 | });
21 |
22 | export default Range;
23 |
--------------------------------------------------------------------------------
/src/components/variables/variable.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Box from '../base/box';
4 | import VariableName from './base/name';
5 | import VariableValue from './base/value';
6 |
7 | const Variable = React.memo(({ variable }) => (
8 |
9 |
10 |
11 |
12 | ));
13 |
14 | export default Variable;
15 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import React from 'react';
3 | import { render } from 'ink';
4 |
5 | import { Provider } from 'react-redux/lib/alternate-renderers';
6 | import store from './redux/store';
7 | import loadUserConfig from './utils/config';
8 |
9 | import App from './app';
10 | import cli from './utils/cli';
11 |
12 | import Theme from './theme';
13 |
14 | const userConfig = loadUserConfig();
15 | const theme = Theme.load(cli.flags.theme, userConfig.theme || {});
16 | const config = Object.assign(userConfig, cli.flags, { theme });
17 |
18 | const ArduinoCereal = ({ config }) => (
19 |
20 |
21 |
22 | );
23 |
24 | render();
25 |
--------------------------------------------------------------------------------
/src/redux/actions/config.js:
--------------------------------------------------------------------------------
1 | import * as types from './types';
2 |
3 | const setConfig = config => ({
4 | type: types.SET_CONFIG,
5 | config
6 | });
7 |
8 | export { setConfig };
9 |
--------------------------------------------------------------------------------
/src/redux/actions/messages.js:
--------------------------------------------------------------------------------
1 | import * as types from './types';
2 |
3 | const setMessageCount = count => ({
4 | type: types.SET_MESSAGE_COUNT,
5 | count
6 | });
7 |
8 | const addMessage = (message, sent = false, time = new Date()) => ({
9 | type: types.ADD_MESSAGE,
10 | message,
11 | sent,
12 | time,
13 | });
14 |
15 | const outputMessages = () => ({
16 | type: types.OUTPUT_MESSAGES
17 | });
18 |
19 | export { setMessageCount, addMessage, outputMessages };
20 |
--------------------------------------------------------------------------------
/src/redux/actions/serial.js:
--------------------------------------------------------------------------------
1 | import * as types from './types';
2 |
3 | const setSerialStatus = status => ({
4 | type: types.SET_SERIAL_STATUS,
5 | status
6 | });
7 |
8 | export { setSerialStatus };
9 |
--------------------------------------------------------------------------------
/src/redux/actions/types.js:
--------------------------------------------------------------------------------
1 | export const SET_CONFIG = 'SET_CONFIG';
2 |
3 | export const SET_SERIAL_STATUS = 'SET_SERIAL_STATUS';
4 |
5 | export const UPDATE_VARIABLE = 'UPDATE_VARIABLE';
6 | export const OUTPUT_VARIABLES = 'OUTPUT_VARIABLES';
7 |
8 | export const ADD_MESSAGE = 'ADD_MESSAGE';
9 | export const OUTPUT_MESSAGES = 'OUTPUT_MESSAGES';
10 | export const SET_MESSAGE_COUNT = 'SET_MESSAGE_COUNT';
11 |
--------------------------------------------------------------------------------
/src/redux/actions/variables.js:
--------------------------------------------------------------------------------
1 | import * as types from './types';
2 |
3 | const updateVariable = (type, name, value, options) => ({
4 | type: types.UPDATE_VARIABLE,
5 | name,
6 | variable: { name, type, value, options }
7 | });
8 |
9 | const outputVariables = () => ({
10 | type: types.OUTPUT_VARIABLES
11 | });
12 |
13 | export { updateVariable, outputVariables };
14 |
--------------------------------------------------------------------------------
/src/redux/reducers/config.js:
--------------------------------------------------------------------------------
1 | import * as types from '../actions/types';
2 |
3 | const initial = {
4 | baud: 9600,
5 | port: undefined,
6 | fps: 15,
7 | theme: {}
8 | };
9 |
10 | export default (state = initial, action) => {
11 | switch (action.type) {
12 | case types.SET_CONFIG:
13 | return {
14 | ...state,
15 | ...action.config
16 | };
17 | default:
18 | return state;
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/src/redux/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import config from './config';
4 | import messages from './messages';
5 | import serial from './serial';
6 | import variables from './variables';
7 |
8 | export default combineReducers({
9 | config,
10 | messages,
11 | serial,
12 | variables
13 | });
14 |
--------------------------------------------------------------------------------
/src/redux/reducers/messages.js:
--------------------------------------------------------------------------------
1 | import * as types from '../actions/types';
2 |
3 | const initial = {
4 | count: 10,
5 | delimiter: '\r\n',
6 | raw: [],
7 | output: [],
8 | terminated: true,
9 | dirty: false
10 | };
11 |
12 | export default (state = initial, action) => {
13 | switch (action.type) {
14 | case types.ADD_MESSAGE: {
15 | if (action.message.length === 0) {
16 | return state;
17 | }
18 |
19 | const { message, time, sent } = action;
20 | const prefix = state.terminated ? '' : (state.raw.pop()?.text || '');
21 | const messages = (prefix + message).split(state.delimiter).map(text => ({ text, time, sent }));
22 | const terminated = message.endsWith(state.delimiter);
23 |
24 | terminated && messages.pop();
25 |
26 | return {
27 | ...state,
28 | terminated,
29 | raw: [...state.raw, ...messages].slice(-state.count),
30 | dirty: true
31 | };
32 | }
33 |
34 | case types.OUTPUT_MESSAGES:
35 | if (!state.dirty) {
36 | return state;
37 | }
38 |
39 | return {
40 | ...state,
41 | output: [...state.raw]
42 | };
43 | case types.SET_MESSAGE_COUNT:
44 | return {
45 | ...state,
46 | count: action.count
47 | };
48 | default:
49 | return state;
50 | }
51 | };
52 |
--------------------------------------------------------------------------------
/src/redux/reducers/serial.js:
--------------------------------------------------------------------------------
1 | import * as types from '../actions/types';
2 |
3 | const initial = {
4 | status: 'pending'
5 | };
6 |
7 | export default (state = initial, action) => {
8 | switch (action.type) {
9 | case types.SET_SERIAL_STATUS:
10 | return {
11 | ...state,
12 | status: action.status
13 | };
14 | default:
15 | return state;
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/src/redux/reducers/variables.js:
--------------------------------------------------------------------------------
1 | import * as types from '../actions/types';
2 |
3 | const initial = {
4 | raw: {},
5 | output: {},
6 | dirty: false
7 | };
8 |
9 | export default (state = initial, action) => {
10 | switch (action.type) {
11 | case types.UPDATE_VARIABLE: {
12 | const { variable } = action;
13 | if (state.raw[action.name]?.value === variable.value) {
14 | return state;
15 | }
16 |
17 | return {
18 | ...state,
19 | raw: {
20 | ...state.raw,
21 | ...{ [action.name]: variable }
22 | },
23 | dirty: true
24 | };
25 | }
26 |
27 | case types.OUTPUT_VARIABLES:
28 | if (!state.dirty) {
29 | return state;
30 | }
31 |
32 | return {
33 | ...state,
34 | output: { ...state.raw }
35 | };
36 | default:
37 | return state;
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/src/redux/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux';
2 | import reducers from '../reducers';
3 |
4 | export default createStore(reducers);
5 |
--------------------------------------------------------------------------------
/src/serial/index.js:
--------------------------------------------------------------------------------
1 | import EventEmitter from 'events';
2 |
3 | import SerialPort from 'serialport';
4 | import CerealParser from './parser';
5 |
6 | export default class Serial extends EventEmitter {
7 | constructor(stream) {
8 | super();
9 |
10 | this.stream = stream;
11 | this.stream.pipe(new CerealParser()).on('data', this._data.bind(this));
12 | this.stream.open(this._open.bind(this));
13 | }
14 |
15 | write(data) {
16 | return this.stream.write(data)
17 | }
18 |
19 | _open(error) {
20 | error === null ? this.emit('connect') : this.emit('error', error);
21 | }
22 |
23 | _data(data) {
24 | this.emit(data.variable ? 'variable' : 'message', data.value);
25 | }
26 |
27 | static connect(port, baud) {
28 | return new this(new SerialPort(port, { baudRate: baud, autoOpen: false }));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/serial/parser.js:
--------------------------------------------------------------------------------
1 | import { Transform } from 'stream';
2 |
3 | export default class CerealParser extends Transform {
4 | constructor(options = {}) {
5 | options.objectMode = true;
6 |
7 | super(options);
8 |
9 | this.delimiter = Buffer.from('c0', 'hex');
10 | this.valueDelimiter = Buffer.from('c1', 'hex');
11 | this.buffer = Buffer.alloc(0);
12 | this.varaible = false;
13 | }
14 |
15 | _transform(chunk, encoding, cb) {
16 | this.buffer = Buffer.concat([this.buffer, chunk]);
17 |
18 | let position;
19 | while ((position = this.buffer.indexOf(this.delimiter)) !== -1) {
20 | const value = this.buffer.slice(0, position).toString('utf-8');
21 | if (value.length > 0) {
22 | this.push({
23 | variable: this.varaible,
24 | value: this.varaible ? value.split(this.valueDelimiter) : value
25 | });
26 | }
27 |
28 | this.varaible = !this.varaible;
29 | this.buffer = this.buffer.slice(position + this.delimiter.length);
30 | }
31 |
32 | if (!this.varaible) {
33 | this._flushBuffer();
34 | }
35 |
36 | cb();
37 | }
38 |
39 | _flush(cb) {
40 | this._flushBuffer();
41 | cb();
42 | }
43 |
44 | _flushBuffer() {
45 | this.push({ variable: false, value: this.buffer.toString('utf-8') });
46 | this.buffer = Buffer.alloc(0);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/theme/index.js:
--------------------------------------------------------------------------------
1 | import merge from 'lodash.merge';
2 |
3 | import light from './presets/light';
4 | import dark from './presets/dark';
5 | import black from './presets/black';
6 | import white from './presets/white';
7 |
8 | const presets = { light, dark, black, white };
9 | export default class Theme {
10 | static load(name, overrides) {
11 | return merge(presets[name] || presets.dark, overrides);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/theme/presets/black.js:
--------------------------------------------------------------------------------
1 | export default {
2 | modules: {
3 | monitor: {
4 | timestamp: true,
5 | timestampLocale: 'en-GB',
6 |
7 | direction: {
8 | received: '<<',
9 | sent: '>>'
10 | }
11 | },
12 |
13 | variables: {
14 | enabled: true,
15 | range: {
16 | fill: '▇',
17 | empty: '-'
18 | },
19 | flag: {
20 | on: '▣',
21 | off: '▢'
22 | }
23 | },
24 |
25 | input: {
26 | enabled: true,
27 | prefix: '$>'
28 | }
29 | },
30 |
31 | colors: {
32 | default: '#000',
33 |
34 | global: {
35 | heading: '#000',
36 | border: '#000'
37 | },
38 |
39 | status: {
40 | success: '#0f0',
41 | error: '#f00'
42 | },
43 |
44 | monitor: {
45 | timestamp: '#000',
46 | message: '#000',
47 |
48 | direction: {
49 | received: '#000',
50 | sent: '#000'
51 | }
52 | },
53 |
54 | variables: {
55 | name: '#000',
56 | value: '#000',
57 |
58 | range: {
59 | fill: '#000',
60 | empty: '#000'
61 | },
62 | flag: {
63 | on: '#000',
64 | off: '#000'
65 | }
66 | }
67 | }
68 | };
69 |
--------------------------------------------------------------------------------
/src/theme/presets/dark.js:
--------------------------------------------------------------------------------
1 | export default {
2 | modules: {
3 | monitor: {
4 | timestamp: true,
5 | timestampLocale: 'en-GB',
6 |
7 | direction: {
8 | received: '<<',
9 | sent: '>>'
10 | }
11 | },
12 |
13 | variables: {
14 | enabled: true,
15 | range: {
16 | fill: '▇',
17 | empty: '-'
18 | },
19 | flag: {
20 | on: '▣',
21 | off: '▢'
22 | }
23 | },
24 |
25 | input: {
26 | enabled: true,
27 | prefix: '$>'
28 | }
29 | },
30 |
31 | colors: {
32 | default: '#839496',
33 |
34 | global: {
35 | heading: '#b58900',
36 | border: '#268bd2'
37 | },
38 |
39 | status: {
40 | success: '#859900',
41 | error: '#dc322f'
42 | },
43 |
44 | monitor: {
45 | timestamp: '#b58900',
46 | message: '#839496',
47 |
48 | direction: {
49 | received: '#b58900',
50 | sent: '#268bd2'
51 | }
52 | },
53 |
54 | variables: {
55 | name: '#839496',
56 | value: '#839496',
57 |
58 | range: {
59 | fill: '#b58900',
60 | empty: '#839496'
61 | },
62 | flag: {
63 | on: '#b58900',
64 | off: '#b58900'
65 | }
66 | }
67 | }
68 | };
69 |
--------------------------------------------------------------------------------
/src/theme/presets/light.js:
--------------------------------------------------------------------------------
1 | export default {
2 | modules: {
3 | monitor: {
4 | timestamp: true,
5 | timestampLocale: 'en-GB',
6 |
7 | direction: {
8 | received: '<<',
9 | sent: '>>'
10 | }
11 | },
12 |
13 | variables: {
14 | enabled: true,
15 | range: {
16 | fill: '▇',
17 | empty: '-'
18 | },
19 | flag: {
20 | on: '▣',
21 | off: '▢'
22 | }
23 | },
24 |
25 | input: {
26 | enabled: true,
27 | prefix: '$>'
28 | }
29 | },
30 |
31 | colors: {
32 | default: '#222222',
33 |
34 | global: {
35 | heading: '#876600',
36 | border: '#104061'
37 | },
38 |
39 | status: {
40 | success: '#859900',
41 | error: '#dc322f'
42 | },
43 |
44 | monitor: {
45 | timestamp: '#876600',
46 | message: '#222222',
47 |
48 | direction: {
49 | received: '#876600',
50 | sent: '#104061'
51 | }
52 | },
53 |
54 | variables: {
55 | name: '#222222',
56 | value: '#876600',
57 |
58 | range: {
59 | fill: '#876600',
60 | empty: '#104061'
61 | },
62 | flag: {
63 | on: '#104061',
64 | off: '#222222'
65 | }
66 | }
67 | }
68 | };
69 |
--------------------------------------------------------------------------------
/src/theme/presets/white.js:
--------------------------------------------------------------------------------
1 | export default {
2 | modules: {
3 | monitor: {
4 | timestamp: true,
5 | timestampLocale: 'en-GB',
6 |
7 | direction: {
8 | received: '<<',
9 | sent: '>>'
10 | }
11 | },
12 |
13 | variables: {
14 | enabled: true,
15 | range: {
16 | fill: '▇',
17 | empty: '-'
18 | },
19 | flag: {
20 | on: '▣',
21 | off: '▢'
22 | }
23 | },
24 |
25 | input: {
26 | enabled: true,
27 | prefix: '$>'
28 | }
29 | },
30 |
31 | colors: {
32 | default: '#fff',
33 |
34 | global: {
35 | heading: '#fff',
36 | border: '#fff'
37 | },
38 |
39 | status: {
40 | success: '#0f0',
41 | error: '#f00'
42 | },
43 |
44 | monitor: {
45 | timestamp: '#fff',
46 | message: '#fff',
47 |
48 | direction: {
49 | received: '#fff',
50 | sent: '#fff'
51 | }
52 | },
53 |
54 | variables: {
55 | name: '#fff',
56 | value: '#fff',
57 |
58 | range: {
59 | fill: '#fff',
60 | empty: '#fff'
61 | },
62 | flag: {
63 | on: '#fff',
64 | off: '#fff'
65 | }
66 | }
67 | }
68 | };
69 |
--------------------------------------------------------------------------------
/src/utils/cli.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import meow from 'meow';
4 |
5 | export default meow(`
6 | Usage
7 | $ arduino-cereal
8 |
9 | Options
10 | --port Serial port
11 | --baud Baud rate (default: 9600)
12 | --theme Theme name (light, dark, black, white)
13 | --fps Screen render rate (default: 15)
14 | `);
15 |
--------------------------------------------------------------------------------
/src/utils/config.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import fs from 'fs';
3 |
4 | export default () => {
5 | const configPath = path.join(process.cwd(), './cereal.config.json');
6 | return fs.existsSync(configPath) ? require(configPath) : {};
7 | };
8 |
--------------------------------------------------------------------------------
/src/utils/use-init.js:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 |
3 | export default (cb = () => {}) => {
4 | const called = useRef(false);
5 | if (called.current) {
6 | return;
7 | }
8 |
9 | cb();
10 | called.current = true;
11 | };
12 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import chalk from 'chalk';
3 | import test from 'ava';
4 | import {render} from 'ink-testing-library';
5 | import App from '../src/app';
6 |
7 | test('greet unknown user', t => {
8 | const {lastFrame} = render();
9 |
10 | t.is(lastFrame(), chalk`Baud rate: {green 9600}`);
11 | });
12 |
13 | test('greet user with a name', t => {
14 | const {lastFrame} = render();
15 |
16 | t.is(lastFrame(), chalk`Baud rate: {green 19200}`);
17 | });
18 |
--------------------------------------------------------------------------------