Compare commits
No commits in common. "e10a4929107f61cbad01683d5866611c5cdd6fef" and "3e50e2bfb499e0e14f2ae1a2d39fdacf5986e503" have entirely different histories.
e10a492910
...
3e50e2bfb4
@ -1,12 +1,9 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@roamhq/wrtc": "0.8.0",
|
||||
"node-dht-sensor": "^0.4.5",
|
||||
"node-pre-gyp": "^0.17.0",
|
||||
"pigpio": "^3.3.1",
|
||||
"sass": "1.86.3",
|
||||
"serialport": "^13.0.0",
|
||||
"ws": "^8.18.0"
|
||||
"ws": "^8.18.0",
|
||||
"sass": "1.86.3"
|
||||
},
|
||||
"scripts": {
|
||||
"build:scss": "npx sass src/static/css:dist/static/css",
|
||||
@ -19,7 +16,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/node-dht-sensor": "^0.4.2",
|
||||
"@types/ws": "^8.5.13",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.2"
|
||||
|
16
sketch.c
16
sketch.c
@ -1,16 +0,0 @@
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
int moisture0 = analogRead(A0);
|
||||
delayMicroseconds(100);
|
||||
analogRead(A1);
|
||||
int moisture1 = analogRead(A1);
|
||||
Serial.print("{\"A0\":");
|
||||
Serial.print(moisture0);
|
||||
Serial.print(",\"A1\":");
|
||||
Serial.print(moisture1);
|
||||
Serial.println("}");
|
||||
delay(1000);
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import * as http from "http";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import IO, { ISensors } from "./io";
|
||||
|
||||
|
||||
export default class HttpServer {
|
||||
@ -11,7 +10,7 @@ export default class HttpServer {
|
||||
private root: string;
|
||||
// public constructor(port: number, root: string, tune: (ch: string, adp?: number) => void, getChannels: ()=>string[], getSignal: (adapter:number)=>object) {
|
||||
|
||||
public constructor(port: number, root: string, io: IO) {
|
||||
public constructor(port: number, root: string) {
|
||||
this.port = port;
|
||||
this.root = root;
|
||||
this.httpServer = http.createServer((req, res) => {
|
||||
@ -26,8 +25,8 @@ export default class HttpServer {
|
||||
switch (req.method) {
|
||||
case "GET":
|
||||
switch (api) {
|
||||
case "sensors":
|
||||
body = JSON.stringify(io.getSensors());
|
||||
case "list":
|
||||
// body = JSON.stringify(getChannels());
|
||||
status = 200;
|
||||
break;
|
||||
case "signal":
|
||||
@ -40,7 +39,7 @@ export default class HttpServer {
|
||||
break;
|
||||
case "PUT":
|
||||
switch (api) {
|
||||
case "power":
|
||||
case "tune":
|
||||
const channel = decodeURIComponent(query[3]);
|
||||
const adapter = parseInt(url.searchParams.get('adapter'));
|
||||
// tune(channel, adapter);
|
||||
|
78
src/io.ts
78
src/io.ts
@ -1,78 +0,0 @@
|
||||
import Serial from "./serial";
|
||||
import * as pigpio from 'pigpio';
|
||||
import * as dht from 'node-dht-sensor';
|
||||
|
||||
type ONOFF = 0 | 1;
|
||||
const ON: ONOFF = 0
|
||||
const OFF: ONOFF = 1
|
||||
|
||||
|
||||
|
||||
export interface ISensors {
|
||||
temperature: number;
|
||||
lights: boolean;
|
||||
heat: boolean
|
||||
moisture0: number;
|
||||
moisture1: number;
|
||||
humidity: number;
|
||||
}
|
||||
|
||||
export default class IO {
|
||||
gpioLights: pigpio.Gpio
|
||||
gpioHeat: pigpio.Gpio
|
||||
serial: Serial;
|
||||
lights: boolean;
|
||||
heat: boolean;
|
||||
getMoisture: () => ({ moisture0: number, moisture1: number });
|
||||
|
||||
public constructor(LIGHTS_GPIO = 17, HEAT_GPIO=22, DHT_GPIO = 27, DHT_MODEL = 22) {
|
||||
const serial = new Serial()
|
||||
this.getMoisture = serial.getMoisture.bind(serial);
|
||||
this.serial = serial;
|
||||
|
||||
const readLights = new pigpio.Gpio(LIGHTS_GPIO, { mode: pigpio.Gpio.INPUT });
|
||||
this.lights = readLights.digitalRead() == ON ? true : false
|
||||
this.gpioLights = new pigpio.Gpio(LIGHTS_GPIO, { mode: pigpio.Gpio.OUTPUT });
|
||||
|
||||
const readHeat = new pigpio.Gpio(HEAT_GPIO, { mode: pigpio.Gpio.INPUT });
|
||||
this.lights = readHeat.digitalRead() == ON ? true : false
|
||||
this.gpioHeat = new pigpio.Gpio(HEAT_GPIO, { mode: pigpio.Gpio.OUTPUT });
|
||||
|
||||
|
||||
dht.initialize(22, DHT_GPIO);
|
||||
dht.setMaxRetries(10);
|
||||
}
|
||||
|
||||
public setPower(state: boolean, GPIO= 17) {
|
||||
if(GPIO == 17){
|
||||
this.lights = state;
|
||||
this.gpioLights.digitalWrite(this.lights ? ON : OFF);
|
||||
}
|
||||
else if(GPIO == 22){
|
||||
this.heat = state;
|
||||
this.gpioHeat.digitalWrite(this.heat ? ON : OFF);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// public togglePower() {
|
||||
// this.power = !this.power;
|
||||
// this.gpio.digitalWrite(this.power ? ON : OFF);
|
||||
// }
|
||||
|
||||
private round = (n) =>
|
||||
(n * 100) / 100
|
||||
|
||||
public getSensors(): ISensors {
|
||||
const { temperature, humidity } = dht.read(22, 27)
|
||||
const { moisture0, moisture1 } = this.getMoisture()
|
||||
return {
|
||||
lights: this.lights,
|
||||
heat: this.heat,
|
||||
moisture0: moisture0,
|
||||
moisture1: moisture1,
|
||||
temperature: this.round(temperature),
|
||||
humidity: this.round(humidity)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
import * as serialport from 'serialport';
|
||||
|
||||
// interface ISerial {
|
||||
// moisture: number;
|
||||
// parser: serialport.ReadlineParser
|
||||
// }
|
||||
|
||||
export default class Serial {
|
||||
private moisture0: number;
|
||||
private moisture1: number;
|
||||
|
||||
public constructor(path = '/dev/ttyUSB0', baudRate = 9600, delimiter = '\n') {
|
||||
const port = new serialport.SerialPort({ path, baudRate });
|
||||
const readline = new serialport.ReadlineParser({ delimiter });
|
||||
const parser = port.pipe(readline);
|
||||
parser.on('data', line => {
|
||||
const data = JSON.parse(line)
|
||||
const moisture0 = parseInt(data['A0'], 10);
|
||||
const moisture1 = parseInt(data['A1'], 10);
|
||||
if (!isNaN(moisture0) && !isNaN(moisture1)) {
|
||||
this.moisture0 = moisture0;
|
||||
this.moisture1 = moisture1;
|
||||
}
|
||||
else {
|
||||
console.error("Error reading analog data")
|
||||
}
|
||||
});
|
||||
port.on('error', err => {
|
||||
console.error('Serial Error:', err.message);
|
||||
});
|
||||
}
|
||||
|
||||
private scale = (value: number) => {
|
||||
const percent = (value / 1024) * 100;
|
||||
return Math.min(Math.max(percent, 0), 100); // Clamp to 0-100 just in case
|
||||
}
|
||||
|
||||
public getMoisture() {
|
||||
return { moisture0: this.scale(this.moisture0), moisture1: this.scale(this.moisture1) };
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import HttpServer from './http';
|
||||
import IO, { ISensors } from './io';
|
||||
import VideoSocket from './ws';
|
||||
|
||||
const HTTP_PORT = process.env.HTTP_PORT ? parseInt(process.env.HTTP_PORT, 10) : 8080;
|
||||
@ -25,16 +24,8 @@ const TV_DEV_0 = process.env.TV_DEV_0 ?? '/dev/video0'
|
||||
// const getSignal = (adapter: number) =>
|
||||
// zap.getSignal(adapter)
|
||||
|
||||
const io = new IO();
|
||||
const getSensors: ()=>ISensors = io.getSensors.bind(io);
|
||||
// setTimeout(()=>{
|
||||
// console.log("sen ",io.getSensors())
|
||||
// console.log("sen2 ",getSensors())
|
||||
// },200)
|
||||
// const setPower = io.setPower.bind(this, true)
|
||||
// const togglePower = io.togglePower.bind(this)
|
||||
const httpServer = new HttpServer(HTTP_PORT, STATIC_ROOT, io);
|
||||
const videoSocket = new VideoSocket(WS_PORT, TV_DEV_0, getSensors);
|
||||
const httpServer = new HttpServer(HTTP_PORT, STATIC_ROOT);
|
||||
const videoSocket = new VideoSocket(WS_PORT, TV_DEV_0);
|
||||
|
||||
httpServer.start();
|
||||
|
||||
@ -42,36 +33,17 @@ httpServer.start();
|
||||
process.stdin.setEncoding("utf8");
|
||||
process.stdin.resume();
|
||||
|
||||
console.log("Menu:\n1)Lights Power off\n2) Lights Power on\n3)Heat Power Off\n4) Heat power on\n5)Read Sensors");
|
||||
console.log("Menu:");
|
||||
|
||||
process.stdin.on("data", async (data: string) => {
|
||||
const input = data.trim();
|
||||
console.log(`Received: "${input}"`);
|
||||
const val = parseInt(input);
|
||||
switch(val) {
|
||||
case 0:
|
||||
// await zap.zapTo(input).then((zap: IZap) => {
|
||||
// console.log(`Tuned ${zap.adapter} to ${zap.channel}`)
|
||||
|
||||
break;
|
||||
case 1:
|
||||
io.setPower(false,17);
|
||||
break;
|
||||
case 2:
|
||||
io.setPower(true,17);
|
||||
break;
|
||||
case 3:
|
||||
io.setPower(false,22);
|
||||
break;
|
||||
case 4:
|
||||
io.setPower(true,22);
|
||||
break;
|
||||
|
||||
|
||||
case 5:
|
||||
console.log("a ", getSensors())
|
||||
break;
|
||||
default:
|
||||
console.log("No option for "+input)
|
||||
}
|
||||
// }).catch((err: Error) => {
|
||||
// console.error(err.message);
|
||||
// });
|
||||
|
||||
});
|
||||
|
||||
|
@ -18,18 +18,12 @@
|
||||
<video id="video0" autoplay playsinline controls></video>
|
||||
<div id="channel-container-0" class="channel-group"></div>
|
||||
</div>
|
||||
<div id="data-container">
|
||||
<!-- <p id="power"></p>
|
||||
<p id="moisture"></p>
|
||||
<p id="temperature"></p>
|
||||
<p id="humidity"></p> -->
|
||||
</div>
|
||||
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/video.js"></script>
|
||||
<script src="js/api.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,9 +0,0 @@
|
||||
const getSensors = () =>{
|
||||
|
||||
}
|
||||
|
||||
const poll = () =>{
|
||||
fetch("/api/sensors").then(r=>r.json()).then(data =>{
|
||||
console.log("data ",data)
|
||||
})
|
||||
}
|
@ -2,24 +2,16 @@ const isSecure = window.location.protocol === 'https:';
|
||||
const host = window.location.hostname;
|
||||
const ws0builder = isSecure ? `wss://${host}/ws3` : `ws://${host}:3003`;
|
||||
const ws0 = new WebSocket(ws0builder);
|
||||
|
||||
interface IData {
|
||||
power: boolean,
|
||||
moisture: number,
|
||||
temperature: number,
|
||||
humidity: number
|
||||
}
|
||||
const dataContainer = document.getElementById('data-container') as HTMLDivElement
|
||||
const config = {
|
||||
const config = {
|
||||
iceServers: [
|
||||
{
|
||||
urls: ['stun:dwestgate.us:3478', 'turn:dwestgate.us:3478?transport=udp'],
|
||||
urls: ['stun:dwestgate.us:3478','turn:dwestgate.us:3478?transport=udp'],
|
||||
username: 'webrtcuser',
|
||||
credential: 'webrtccred'
|
||||
}
|
||||
]
|
||||
};
|
||||
const pc0 = new RTCPeerConnection(isSecure ? config : {});
|
||||
const pc0 = new RTCPeerConnection(config);
|
||||
const video0 = document.getElementById('video0') as HTMLVideoElement;
|
||||
|
||||
|
||||
@ -34,27 +26,6 @@ pc0.onicecandidate = ({ candidate }) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Create the data channel (client initiates)
|
||||
const dataChannel = pc0.createDataChannel('sensors');
|
||||
console.log("📡 Data channel created by client");
|
||||
|
||||
dataChannel.onopen = () => {
|
||||
console.log('📬 Client: Data channel opened');
|
||||
};
|
||||
|
||||
dataChannel.onmessage = (event) => {
|
||||
// console.log("📦 Client received message:", event.data);
|
||||
const json = JSON.parse(event.data) ;
|
||||
|
||||
dataContainer.innerHTML = Object.entries(json).map(([k,v])=>`<p>${k}:${v}</p>`).join('')
|
||||
|
||||
};
|
||||
|
||||
dataChannel.onclose = () => {
|
||||
console.log("📴 Client: Data channel closed");
|
||||
};
|
||||
|
||||
ws0.onopen = async () => {
|
||||
pc0.addTransceiver('video', { direction: 'recvonly' });
|
||||
pc0.addTransceiver('audio', { direction: 'recvonly' })
|
||||
@ -66,7 +37,6 @@ ws0.onopen = async () => {
|
||||
ws0.onmessage = async (message) => {
|
||||
const msg = JSON.parse(message.data);
|
||||
if (msg.type === 'answer') {
|
||||
// pc0.data
|
||||
await pc0.setRemoteDescription(msg.data);
|
||||
}
|
||||
else if (msg.type === 'ice-candidate') {
|
||||
@ -74,4 +44,3 @@ ws0.onmessage = async (message) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
51
src/ws.ts
51
src/ws.ts
@ -1,7 +1,6 @@
|
||||
import { MediaStream, MediaStreamTrack, nonstandard, RTCPeerConnection, RTCDataChannel } from '@roamhq/wrtc';
|
||||
import { MediaStream, MediaStreamTrack, nonstandard, RTCPeerConnection } from '@roamhq/wrtc';
|
||||
import { ChildProcessWithoutNullStreams, spawn } from 'child_process';
|
||||
import * as ws from 'ws';
|
||||
import { ISensors } from './io';
|
||||
|
||||
// Constants
|
||||
const WIDTH = 640; // Video width
|
||||
@ -10,7 +9,7 @@ const FRAME_SIZE = WIDTH * HEIGHT * 1.5; // YUV420P frame size
|
||||
|
||||
export default class VideoSocket {
|
||||
videoDevice: string;
|
||||
public constructor(port: number, videoDevice, getSensors: () => ISensors) {
|
||||
public constructor(port: number, videoDevice) {
|
||||
this.videoDevice = videoDevice
|
||||
const ffmpegProcess = this.startFFmpeg();
|
||||
const videoTrack = this.createVideoTrack(ffmpegProcess);
|
||||
@ -26,35 +25,11 @@ export default class VideoSocket {
|
||||
|
||||
wss.on('connection', async (ws: ws.WebSocket) => {
|
||||
const peerConnection: RTCPeerConnection = this.createPeerConnection(videoTrack, audioTrack);
|
||||
// The client created the data channel. The server should access it as follows:
|
||||
peerConnection.ondatachannel = (event) => {
|
||||
const dataChannel = event.channel; // This is the data channel created by the client
|
||||
|
||||
dataChannel.onopen = () => {
|
||||
console.log('📬 Server: Data channel opened');
|
||||
// Now you can send data through the channel
|
||||
setInterval(() => {
|
||||
const sensorData = getSensors(); // Example function to fetch data
|
||||
dataChannel.send(JSON.stringify(sensorData)); // Send data to the client
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
dataChannel.onmessage = (event) => {
|
||||
console.log("📦 Server received message:", event.data);
|
||||
};
|
||||
|
||||
dataChannel.onclose = () => {
|
||||
console.log("📴 Server: Data channel closed");
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
ws.on('message', async (message: Buffer) => {
|
||||
const { type, data } = JSON.parse(message.toString());
|
||||
|
||||
if (type == 'offer') {
|
||||
|
||||
await peerConnection.setRemoteDescription(data);
|
||||
const answer = await peerConnection.createAnswer();
|
||||
await peerConnection.setLocalDescription(answer);
|
||||
@ -206,28 +181,6 @@ export default class VideoSocket {
|
||||
return audioSource.createTrack();
|
||||
}
|
||||
|
||||
createDataChannel = (peerConnection: RTCPeerConnection, getSensors: () => any) => {
|
||||
const dataChannel = peerConnection.createDataChannel('sensors')
|
||||
console.log("create data channel");
|
||||
dataChannel.onopen = () => {
|
||||
console.log('✅ Data channel is open');
|
||||
// Send dummy JSON for testing
|
||||
setInterval(() => {
|
||||
const sensorData = getSensors(); // Assuming getSensors returns JSON
|
||||
dataChannel.send(JSON.stringify(sensorData));
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
dataChannel.onerror = (error) => {
|
||||
console.error('❌ DataChannel error:', error);
|
||||
};
|
||||
|
||||
dataChannel.onclose = () => {
|
||||
console.log('❎ DataChannel closed');
|
||||
};
|
||||
return peerConnection;
|
||||
}
|
||||
|
||||
createPeerConnection = (videoTrack: MediaStreamTrack, audioTrack: MediaStreamTrack): RTCPeerConnection => {
|
||||
const peerConnection = new RTCPeerConnection({
|
||||
iceServers: [
|
||||
|
Loading…
Reference in New Issue
Block a user