Mqx
Mqx2y ago

Use Deno.run() to communicate with Arduino

Hey I'm trying to communicate via USB serial with my Arduino. My goal is to have the result I get via USB serial available as a variable in Deno. I tried to do the whole thing with Deno.run(). This is my Arduino main file:
#define POTENTIOMETER_PIN_A A0

void setup() {
pinMode(POTENTIOMETER_PIN_A, INPUT);
Serial.begin(115200);
}

void loop() {
int potentiometerA = analogRead(POTENTIOMETER_PIN_A);

String data = map(potentiometerA, 0, 1023, 0, 100) + String('\n');

Serial.write(data.c_str());

delay(100);
}
#define POTENTIOMETER_PIN_A A0

void setup() {
pinMode(POTENTIOMETER_PIN_A, INPUT);
Serial.begin(115200);
}

void loop() {
int potentiometerA = analogRead(POTENTIOMETER_PIN_A);

String data = map(potentiometerA, 0, 1023, 0, 100) + String('\n');

Serial.write(data.c_str());

delay(100);
}
With this simple PowerShell script I can read the data from USB serial:
$port= new-Object System.IO.Ports.SerialPort COM4,115200,None,one
$port.Open()
do {
$line = $port.ReadLine()
Write-Host $line
}
while ($port.IsOpen)
$port= new-Object System.IO.Ports.SerialPort COM4,115200,None,one
$port.Open()
do {
$line = $port.ReadLine()
Write-Host $line
}
while ($port.IsOpen)
How can I run the whole thing in Deno now and get the output? I have tried the following which obviously does not work:
Deno.run({
cmd: [
'powershell',
'$port= new-Object System.IO.Ports.SerialPort COM4,115200,None,one',
'$port.Open()',
'do { $line = $port.ReadLine(); Write-Host $line }',
'while ($port.IsOpen)'
]
})
Deno.run({
cmd: [
'powershell',
'$port= new-Object System.IO.Ports.SerialPort COM4,115200,None,one',
'$port.Open()',
'do { $line = $port.ReadLine(); Write-Host $line }',
'while ($port.IsOpen)'
]
})
Can anyone help me with this? Or is there perhaps a better solution for that? Thanks!
51 Replies
AapoAlas
AapoAlas2y ago
You'd have to write the script in a .ps file and run powershell with that script file as the command argument.
Mqx
Mqx2y ago
Okay like this?: usbSerial.ps1
$port = new-Object System.IO.Ports.SerialPort COM4, 115200, None, 8, one

function open {
$port.Open()

while ($port.IsOpen) {
$line = $port.ReadLine()
Write-Host $line
}
}

function close {
$port.Close()

}

open
$port = new-Object System.IO.Ports.SerialPort COM4, 115200, None, 8, one

function open {
$port.Open()

while ($port.IsOpen) {
$line = $port.ReadLine()
Write-Host $line
}
}

function close {
$port.Close()

}

open
main.ts
const process = Deno.run({
cmd: [
'powershell', './usbSerial.ps1'
],
stdout: "piped"
})
const process = Deno.run({
cmd: [
'powershell', './usbSerial.ps1'
],
stdout: "piped"
})
But how do I get the output?
AapoAlas
AapoAlas2y ago
IIRC the process will contain a readable stream in it.
Mqx
Mqx2y ago
Okay I get the readable stream using this line:
const outputStream = process.stdout.readable.getReader()
const outputStream = process.stdout.readable.getReader()
How can I get my value from that?
AapoAlas
AapoAlas2y ago
Lookup WebStreams API and specifically TextDecoderStream: https://developer.mozilla.org/en-US/docs/Web/API/TextDecoderStream I presume your Write-Host $line there is writing essentially text values, then you can use TextDecoderStream to turn the process.stdout.readable output Uint8Array into readable strings in JS.
TextDecoderStream - Web APIs | MDN
The TextDecoderStream interface of the Encoding API converts a stream of text in a binary encoding, such as UTF-8 etc., to a stream of strings. It is the streaming equivalent of TextDecoder.
Mqx
Mqx2y ago
Thanks I look into this! The only thing I get are Uint8Arrays like this: Uint8Array(3) [ 49, 48, 48 ]
AapoAlas
AapoAlas2y ago
Yes, the stdout is bytes (output of programs doesn't need to be textual), so you use the TextDecoderStream to turn it into text. Except if you want the plain values then you'll probably want to use the Uint8Arrays directly. In this case it might eg. be that the data being read from the serial port is those sequential numbers: 49, changed to 48, and then 48 again.
Mqx
Mqx2y ago
No not really the only thing that I am sending from the Arduino is the plain number. It is the percentage of the potentiometer. And If I am running the ps file directly I get the plain value And I also changed Write-Host $line to Write-Output $line but that does change nothing...
AapoAlas
AapoAlas2y ago
What is the "plain value"?
Mqx
Mqx2y ago
Serial.write(data.c_str());
Serial.write(data.c_str());
34
34
AapoAlas
AapoAlas2y ago
Does it happen to be 100? 🙂
new TextDecoder().decode(new Uint8Array([49, 48, 48])) "100"
Mqx
Mqx2y ago
The Values do not change. Okay I get the value now but the value does not change. Only when I am restarting the script.
const process = Deno.run({
cmd: [
'powershell', './usbSerial.ps1'
],
stdout: "piped"
})

const outputStream = process.stdout.readable.getReader()

if (outputStream) {
const result = await outputStream.read()
const value = new TextDecoder().decode(result.value)
while (value) {
console.log(value)
}
}
const process = Deno.run({
cmd: [
'powershell', './usbSerial.ps1'
],
stdout: "piped"
})

const outputStream = process.stdout.readable.getReader()

if (outputStream) {
const result = await outputStream.read()
const value = new TextDecoder().decode(result.value)
while (value) {
console.log(value)
}
}
What I am doing wrong? Okay this does kind of work:
if (outputStream) {
while (true) {
const result = await outputStream.read()
const value = new TextDecoder().decode(result.value)
console.log(value)
}
}
if (outputStream) {
while (true) {
const result = await outputStream.read()
const value = new TextDecoder().decode(result.value)
console.log(value)
}
}
How can I make it so that it reads the steam continuous? This only reads on time and that stops.
outputStream.read().then(data => {
return new TextDecoder().decode(data.value)
}).then(value => {
console.log(value);
})
outputStream.read().then(data => {
return new TextDecoder().decode(data.value)
}).then(value => {
console.log(value);
})
AapoAlas
AapoAlas2y ago
So, async streams can be read in a number of ways. while (true) is one, you can also do:
for await (const result of stream) {
// ...
}
for await (const result of stream) {
// ...
}
And, as said earlier you can use TextDecoderStream to do something like this:
const { writable, readable } = new TextDecoderStream();

process.stdout.readable.pipeTo(writable);

for await (const decodedValue of readable) {
console.log(decodedValue);
}
const { writable, readable } = new TextDecoderStream();

process.stdout.readable.pipeTo(writable);

for await (const decodedValue of readable) {
console.log(decodedValue);
}
Mqx
Mqx2y ago
Ahh okay Would it be more efficient if I send a command to the arduino to read the potentiometer and that returns this value? Instead of continuous sending the data from the arduino?
AapoAlas
AapoAlas2y ago
Hmm, well, I don't know too much about Arduino so I cannot know really. I don't expect the stream to be a terrible resource hog but I may well be wrong. Checking with eg. htop on Linux or Task Manager on Windows should give you good indication. I guess at its core it depends on your use-case.
Mqx
Mqx2y ago
I want to build a MacroPad
AapoAlas
AapoAlas2y ago
Oh, and if your "send a command" means starting a process using Deno.run then I'm quite sure it's more efficient to keep the stream.
Mqx
Mqx2y ago
Okay Only Problem is that Deno or lets say JS is to fast... So It prints empty lines
AapoAlas
AapoAlas2y ago
Starting a program tends to be much more performance intensive than keeping one running 🙂
Mqx
Mqx2y ago
Okay
AapoAlas
AapoAlas2y ago
You could do if (decodedValue) { console.log(decodedValue) } to avoid printing empty lines.
Mqx
Mqx2y ago
okay Hmm it still prints empty lines... That was the key:
if (decodedValue.trim() != '') {
console.log(decodedValue);
}
if (decodedValue.trim() != '') {
console.log(decodedValue);
}
Is it possible to call a function inside of the powershell script using Deno? That way I could create one function to open, sending/receiving and closing the connection
AapoAlas
AapoAlas2y ago
Well, the Deno.run does have a writable which you could use to send data to stdin for the powershell script, but I have no idea how you could use that in the script to get messages from Deno and send them onwards to Arduino.
Mqx
Mqx2y ago
Hmm is it possible for the shell to listen for instructions?
Mqx
Mqx2y ago
Stack Overflow
Calling a specific PowerShell function from the command line
I have a PowerShell script that contains several functions. How do I invoke a specific function from the command line? This doesn't work: powershell -File script.ps1 -Command My-Func
AapoAlas
AapoAlas2y ago
Sorry, I do not know.
Mqx
Mqx2y ago
Hmm okay The reason why I want to do this is, because I have his global scope where I create the $port = new-Object System.IO.Ports.SerialPort COM4, 115200, None, 8, one object And I want to use the ps file like a wrapper so that I can send instructions only if needed.
AapoAlas
AapoAlas2y ago
Maybe search for something about reading stdin in powershell, and then decide based on stdin what your script will do. Then in your Deno code you can do writableStreamWriter.write("command")
Mqx
Mqx2y ago
👍🏻 Found this:
function read-hostuntilflag([String] $flag) {
while (($line = read-host) -cne $flag) { $line }
}
function read-hostuntilflag([String] $flag) {
while (($line = read-host) -cne $flag) { $line }
}
But I am not sure if read-host = stdin How do I write to the writable?
writable.getWriter().write()
writable.getWriter().write()
and than?
process.stdin?.writable.getWriter().write(new TextEncoder().encode('getValues()'))
process.stdin?.writable.getWriter().write(new TextEncoder().encode('getValues()'))
This does not work how I would expect it... Okay I have done some tests... and the Arduino is so **** slow while responding... Basically I send an instruction and the Arduino responds like a second later...
AapoAlas
AapoAlas2y ago
Hmm, might be related to the whole deno -> stdin -> script -> arduino -> script -> stdout -> deno loop, or it might just be a slow Arduino update loop or something 🙂
Mqx
Mqx2y ago
I think it is the Arduion The delay also exists if I run the ps script directly
AapoAlas
AapoAlas2y ago
That's good to hear (from a Deno standpoint, not good for you though 🙂 )
Mqx
Mqx2y ago
yea I think I am just gonna write constantly from the Arduino. That is the easiest solution...
AapoAlas
AapoAlas2y ago
BTW, it might be possible to open the serial port directly from Deno. I'm not quite sure here but Deno.openSync("\\\.\\COM4", { read: true, write: true, createNew: true }) might work. Or something to that effect.
Mqx
Mqx2y ago
Is this a new feature? Never seen it Ahh okay Yea I think I have tried that YES That also works nice! Only problem is that I get these weird line breaks
{
a:36,b:50}

{
a:36,b:50}

{
a:3
6
,b:50}
{
a:36,b:50}

{
a:36,b:50}

{
a:3
6
,b:50}
How it should look like:
{a:36,b:50}
{a:36,b:50}
AapoAlas
AapoAlas2y ago
Hmm, you might need to buffer the data when you read it. Right now you have just console.log(data), right? Instead you'd do something like this:
let result: string = "";
for await (const data of stream) {
const trimmedData = data.trim();
if (!trimmedData) { continue; }
result = `${result}${trimmedData}`;
if (result.startsWith("{") && result.endsWith("}")) {
console.log(result);
result = "";
}
}
let result: string = "";
for await (const data of stream) {
const trimmedData = data.trim();
if (!trimmedData) { continue; }
result = `${result}${trimmedData}`;
if (result.startsWith("{") && result.endsWith("}")) {
console.log(result);
result = "";
}
}
So here you put the messages from the stream into a single result string until you have a complete message, starting with { and ending with } (theoretically it's possible that this would give you multiple messages in a single string but it's probably unlikely). Then you log that message, or if want to eg. call some callback then you'll call that, and then reset the string to an empty string to start gathering up the next message all over again.
Mqx
Mqx2y ago
👍🏻 thanks Okay that somehow does not work anymore :D but what I am currently trying is to async read/write inside of the powershell. Only problem is that Read-Host is a promt instruction and so the script waits at there.
AapoAlas
AapoAlas2y ago
Huuuhh... 😄 Always save your work in a functional state to make sure you don't forget how you got it to work in the first place 🙂
Mqx
Mqx2y ago
yea :D
AapoAlas
AapoAlas2y ago
(I always make that mistake, always keep writing without making a commit and then I have no idea where the previous "good state" was 😄 )
Mqx
Mqx2y ago
Yea but thats not the problem... I do like the method with the stream much more... instead of opening the port
AapoAlas
AapoAlas2y ago
You can create streams from the Deno.openFile though?
Mqx
Mqx2y ago
yea but that does not work correctly. Sometimes it works, sometimes it does not Currently I stick to the powershell method
AapoAlas
AapoAlas2y ago
If I had to guess it'd be that sometimes the file already exists / is opened by a previous Deno still dying or by your PS script. It might help if you forcibly delete the file first.
Mqx
Mqx2y ago
yea Okay have found a reliable method. I use this PS script:
param ($port, $baudrate)

$portObject = new-Object System.IO.Ports.SerialPort $port, $baudrate, None, 8, one

$portObject.Open()

while ($portObject.IsOpen) {
$instruction = Read-Host
$portObject.WriteLine($instruction)
Write-Output $portObject.ReadLine()
}
param ($port, $baudrate)

$portObject = new-Object System.IO.Ports.SerialPort $port, $baudrate, None, 8, one

$portObject.Open()

while ($portObject.IsOpen) {
$instruction = Read-Host
$portObject.WriteLine($instruction)
Write-Output $portObject.ReadLine()
}
That way I can send an instruction and receive it. I also found the issue why the arduino is so slow... I have used Serial.readString() an this is a really inefficient way to read in data Why are these not the same...?
void loop() {

if (Serial.available() > 0) {
char instruction = Serial.read();

switch (instruction) {
default:
Serial.write(instruction); <-- This works
break;

case 'a':
Serial.write("getValues()\0");
break;
}
}
}
void loop() {

if (Serial.available() > 0) {
char instruction = Serial.read();

switch (instruction) {
default:
Serial.write(instruction); <-- This works
break;

case 'a':
Serial.write("getValues()\0");
break;
}
}
}
void loop() {

if (Serial.available() > 0) {
char instruction = Serial.read();

switch (instruction) {
default:
Serial.write("instruction\0"); <-- This does not work?!
break;

case 'a':
Serial.write("getValues()\0");
break;
}
}
}
void loop() {

if (Serial.available() > 0) {
char instruction = Serial.read();

switch (instruction) {
default:
Serial.write("instruction\0"); <-- This does not work?!
break;

case 'a':
Serial.write("getValues()\0");
break;
}
}
}
It just stops there
AapoAlas
AapoAlas2y ago
Hmm, no idea. I've never done PS scripts.
Mqx
Mqx2y ago
No its c++ Thats my Arduino file I want to send a instruction with deno and than the ino file returns the result the top code works fine
AapoAlas
AapoAlas2y ago
Oh. Hmm... Does the getValues side work? Does your top code ever run that path?
Mqx
Mqx2y ago
The "getValues()" is just a sting that I print Its just a placeholder
AapoAlas
AapoAlas2y ago
Yes, I mean to ask if the Serial.write call works as you expect on that path.
Mqx
Mqx2y ago
Yea I think so. If I send 'a' it responds with "getValues()" and on any other instruction it responds with the instruction I send but if I want to send a string and "not use" the instruction value, it stops And I dont want to send back the instruction every time... I dont like C++ xD