began documentation in readme

by default removed root namespace checking must now set useRootNS to true to access command functions there.  This to avoid issuess like an 'on' function clashing with an emitter listerner.
master
David Kebler 2018-05-25 14:39:48 -07:00
parent ea8253f4b0
commit 10077e207b
6 changed files with 140 additions and 137 deletions

View File

@ -3,125 +3,83 @@ import Base from '../src/base'
// const USOCKET = __dirname + '/sample.sock' // const USOCKET = __dirname + '/sample.sock'
const socketfuncs = { const socketfuncs = {
write: { switch: {
happy: function(packet){ on: function(packet){
return new Promise( async (resolve) => { return new Promise( async (resolve) => {
let res = {} packet.cmd='status/on'
res.response=packet.data+ ' and Im a happy puppy :)' this.push(packet) // push to all active socket servers
let res = { response:'status pushed on to all clients'}
return resolve(res) return resolve(res)
}) })
}, },
sad: function(packet){ off: function(packet){
return new Promise( async (resolve) => { return new Promise( async (resolve) => {
let res = {} packet.cmd='status/off'
res.response='Im a sad dog :(' this.push(packet) // push to all active socket servers
let res = { response:'status pushed off to all clients'}
return resolve(res)
})
},
toggle: function(packet){
return new Promise( async (resolve) => {
// would check status before deciding what to push
if (packet.status===null) packet.status = Math.random()>=.5 ? 'on' : 'off'
else packet.status = (packet.status==='on' ? 'off' : 'on')
packet.cmd ='status/'+ packet.status
this.push(packet) // push to all active socket servers
let res = { cmd:'reply', response:`command ${packet.cmd} with id:${packet.id} pushed to all consumers(clients)`}
return resolve(res) return resolve(res)
}) })
} }
} }
} }
const clientfuncs = {
reply: packet => {
const tcpfuncs = { return new Promise( async (resolve) => {
write: { console.log('==============Reply Processor at Consumer==========')
happy: function(packet){ console.dir(packet.response)
return resolve()
})
},
status: {
on: packet => {
return new Promise( async (resolve) => { return new Promise( async (resolve) => {
let res = {} console.log('==============Pushed to Consumer==========')
res.cmd='reply' console.log(`Switch ${packet.id} is on`)
res.response=packet.data+ ' and Im a TCP happy puppy :)' return resolve()
return resolve(res)
}) })
}, },
sad: function(packet){ off: packet => {
return new Promise( async (resolve) => { return new Promise( async (resolve) => {
let res = {} console.log('==============Pushed to Consumer==========')
res.cmd='reply' console.log(`Switch ${packet.id} is off`)
res.response='Im a TCP sad dog :(' return resolve()
return resolve(res)
}) })
} }
} }
} }
let fio = new Base({sockets:'uc#c>n,us#s>n,tc#c>t,ts#s>t,mqtts#s>m,webs#s>w', tc:{port:8100}, ts:{port:8100}, webs:{ port:8090 }, mqtts:{ topics:['switch/#']}, id:'four-in-one'})
// const delay = time => new Promise(res=>setTimeout(()=>res(),time)) fio.s = socketfuncs
fio.c = clientfuncs
; ;
(async () => { (async () => {
let fio = new Base({sockets:'uc#c>n,us#s>n,tc#c>t,ts#s>t,mqtts#s>m,webs#s>w', webs:{ port:8090 }, mqtts:{ topics:'write'}, id:'four-in-one'})
// let fio = new Base({sockets:'uc#c>n,us#s>n', id:'four-in-one'})
let err = await fio.init()
// console.log(err)
if(!err.find(socket => {
console.log(socket.name)
return socket.name==='mqtts'}))
{ let res = await fio.init()
const mqttProcess = async function (packet,topic) { console.log('initialize errors',res)
console.log('==============mqtt incoming packet/topic processor =========') console.log('waiting for packets')
console.log(packet, topic, fio.s[topic][packet.cmd])
if (packet.cmd) console.log(await fio.s[topic][packet.cmd](packet))
console.log('===========================')
}
fio.socket.mqtts.registerPacketProcessor(mqttProcess)
}
fio.s = socketfuncs
fio.st = tcpfuncs
fio.ct = {reply: packet =>{
console.log('==============Packet Displayed for TCP consumer received packets only')
console.dir(packet.response)
console.log('===========================')
}}
fio.cn = {
reply: function (packet) {
console.log('==============Replay for only Named Pipe Consumer=========')
console.dir(packet.response)
console.log('===========================')
}
}
fio.good = {
bad: function(packet){
return new Promise( async (resolve) => {
let res = {}
res.req = packet
res.cmd='good/ugly'
res.response='The Good The Bad and The Ugly'
return resolve(res)
})
},
ugly: function (packet) {
console.log('==============reply from Good Bad command =========')
console.log(packet.response)
console.log('===========================')
}
}
let packet = {} let packet = {}
console.log('=============sending============') console.log('=============sending============')
packet = {cmd:'echo', data:'some data to echo'} packet = {cmd:'switch/on', id:'1'}
console.log(packet) console.log('sending to socket uc',packet)
await fio.send(packet,'uc') await fio.send('uc',packet)
packet = {cmd:'write:happy', data:'My name is Zoe'}
console.log(packet)
console.log(await fio.send(packet))
console.log(fio.getPacketByName('uc',await fio.send(packet)).response)
packet = {cmd:'write:sad', data:'data to write'}
console.log(packet)
await fio.send(packet)
packet = {cmd:'write:sad', data:'sent only via tcp'}
console.log(packet)
console.log(fio.getPacketByName('tc',await fio.sendTCP(packet)))
packet = {cmd:'good:bad'}
console.log(packet)
await fio.send(packet)
// process.kill(process.pid, 'SIGTERM')
})().catch(err => { })().catch(err => {
console.error('FATAL: UNABLE TO START SYSTEM!\n',err) console.error('FATAL: UNABLE TO START SYSTEM!\n',err)

View File

@ -2,7 +2,7 @@ import Base from '../src/base'
// const USOCKET = __dirname + '/sample.sock' // const USOCKET = __dirname + '/sample.sock'
let dy = new Base({id:'dynamic'}) let dy = new Base({id:'websocket'})
dy.switch = { dy.switch = {
on: function(packet){ on: function(packet){
@ -18,13 +18,15 @@ dy.switch = {
}, },
off: function(packet){ off: function(packet){
return new Promise( async (resolve) => { return new Promise( async (resolve) => {
console.log(packet)
console.log(`turning switch off for id ${packet.id||packet.data}`) console.log(`turning switch off for id ${packet.id||packet.data}`)
// call switch on here // call switch on here
let res = {} let res = {}
res.cmd='switch/status' res.cmd='switch/status'
res.status='off' res.status='off'
res.id = packet.id res.id = packet.id
console.log('broadcast',res)
this.push(res)
res = { cmd:'ack'}
return resolve(res) return resolve(res)
}) })
} }
@ -35,8 +37,7 @@ dy.switch = {
(async () => { (async () => {
await dy.init() await dy.init()
console.log('started', dy.started) await dy.addSocket('web','s','w')
// await Promise.all([dy.addSocket('mqc','c','m'),dy.addSocket('mqs','s','m')])
await dy.addSocket('mqs','s','m') await dy.addSocket('mqs','s','m')
dy.socket.mqs.subscribe(['switch/on','switch/off','switch/toggle']) dy.socket.mqs.subscribe(['switch/on','switch/off','switch/toggle'])

View File

@ -1,7 +1,7 @@
{ {
"name": "@uci/base", "name": "@uci/base",
"version": "0.1.4", "version": "0.1.6",
"description": "Mutli Level/Transport Message/Event Classes", "description": "Multi type and transport JSON packet communication base class. Used in UCI extended classes",
"main": "src/base", "main": "src/base",
"scripts": { "scripts": {
"deve": "./node_modules/.bin/nodemon -r esm examples/four-in-one", "deve": "./node_modules/.bin/nodemon -r esm examples/four-in-one",
@ -33,17 +33,17 @@
"chai": "^4.1.2", "chai": "^4.1.2",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"codecov": "^3.0.0", "codecov": "^3.0.0",
"esm": "^3.0.36", "esm": "^3.0.37",
"istanbul": "^0.4.5", "istanbul": "^0.4.5",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"nodemon": "^1.14.12" "nodemon": "^1.14.12"
}, },
"dependencies": { "dependencies": {
"@uci/logger": "^0.0.3", "@uci/logger": "^0.0.3",
"@uci/mqtt": "0.0.9", "@uci/mqtt": "^0.1.0",
"@uci/socket": "^0.1.1", "@uci/socket": "^0.1.4",
"@uci/utils": "^0.1.1", "@uci/utils": "^0.1.1",
"@uci/websocket": "^0.1.8", "@uci/websocket": "^0.1.9",
"p-settle": "^2.1.0" "p-settle": "^2.1.0"
} }
} }

View File

@ -1,23 +1,56 @@
# uCOMmandIt Base Class for all Classes # uCOMmandIt (UCI) - An Extendable Inter Process Communication Base Class
## Supports multi type and transport communication via a JSON packet.
*used as the basis of many of the UCI library classes*
<!-- find and replace the package name to match --> <!-- find and replace the package name to match -->
[![Build Status](https://img.shields.io/travis/uCOMmandIt/uci-pkg-template.svg?branch=master)](https://travis-ci.org/uCOMmandIt/uci-pkg-template) [![Build Status](https://img.shields.io/travis/uCOMmandIt/uci-base.svg?branch=master)](https://travis-ci.org/uCOMmandIt/uci-base)
[![Inline docs](http://inch-ci.org/github/uCOMmandIt/uci-pkg-template.svg?branch=master)](http://inch-ci.org/github/uCOMmandIt/uci-pkg-template) [![Inline docs](http://inch-ci.org/github/uCOMmandIt/uci-base.svg?branch=master)](http://inch-ci.org/github/uCOMmandIt/uci-base)
[![Dependencies](https://img.shields.io/david/uCOMmandIt/uci-pkg-template.svg)](https://david-dm.org/uCOMmandIt/uci-pkg-template) [![Dependencies](https://img.shields.io/david/uCOMmandIt/uci-base.svg)](https://david-dm.org/uCOMmandIt/uci-base)
[![devDependencies](https://img.shields.io/david/dev/uCOMmandIt/uci-pkg-template.svg)](https://david-dm.org/uCOMmandIt/uci-pkg-template?type=dev) [![devDependencies](https://img.shields.io/david/dev/uCOMmandIt/uci-base.svg)](https://david-dm.org/uCOMmandIt/uci-base?type=dev)
[![codecov](https://img.shields.io/codecov/c/github/uCOMmandIt/uci-pkg-template/master.svg)](https://codecov.io/gh/uCOMmandIt/uci-pkg-template) [![codecov](https://img.shields.io/codecov/c/github/uCOMmandIt/uci-base/master.svg)](https://codecov.io/gh/uCOMmandIt/uci-base)
Base Class extended for all UCI Classes
changed sockets options now can add as many as you want of either type and transport ## What is it
improved socket packet processing call now binds this and name of socket that received packet for processing
single common processor for all packets incoming (either consumer or socket) This module contains an JSON packet socket communication ES6 class. By extending this class you can bake in pretty much any conceivable inter process communication from named pipe to mqtt into your own class and instances. Your extended class can be both a socket(server) and a consumer(client) at the same time which allows you to listen for incoming packets, process them and then send or push them on to other processes running either locally or remotely. Further you can, at runtime, create additional sockets and consumers.
default processing splits into socket or consumer
default processing has calls looking for packet cmd functions before returning error packet ## What's it good for
1. Added Namespace // TODO
2. A Pariticular Sockect By extending this class you can create all manner of "smart" processes that an take action or request action from/to another process (either local or remote) via a simple JSON packet. So it's good for....most ANYTHING. Need to talk to an I2C device on some SBC then create an instance of an I2C-device class that talks to an instance of an I2C bus class both of which are extended from this UCI base class. Now create a "master controller" from the base class that takes in command packets from say a [websocket browser]() or an mqtt interface like [Home Assitant](https://www.home-assistant.io/) and boom you are controlling some I2C device from your browser or home assistant with a click. But that's already been done for you as both as UCI has both an i2c-device class and a UCI i2c-bus class available [UCI @github](https://github.com/uCOMmandIt) or [UCI @npm](https://www.npmjs.com/search?q=scope%3Auci) or [UCI @npms.io](https://npms.io/search?q=scope%3Auci)
3. Root of instance
4. The socket type and transport //TODO ## Prerequisites
5. The socket type
there are helper functions for adding cmd functions to any of these save the root instance we you can add directly. You'll of course need nodejs running and either npm or yarn. UCI tries to keep node as current as possible during development so use the latest version 10.x for best results or at a minimum the last version of 9.x. Getting these prerequistes set up is beyond the scope of this readme. One pretty easy way is using [node version manager](https://github.com/creationix/nvm) which makes it easy to swap between versions of node if need be (not supported in Windows but see their suggested alternative)
## OS Support
UCI was developed primarly for IOT (internet of things) and SBCs (single board computers) like the Raspberry Pi that run linux and nodejs. As such there was NO effort made to confirm that this base class or any of its extensions will work on a machine running Windows or OSX. You are welcome to try. I predict no issues on OSX but definitely using 'named pipes' on Windows will likely not work without some changes to the @uci/socket module.
## Terminology
While developing the UCI library the following terminolgy was adopted to describe a communication connection through which the JSON packets can potentially travel. Each connection is specified with a `type` and a `transport`
**type**: can be either `s` or `c`. `s` refers to either **s**ocket or **s**erver and `c` refers to either **c**onsumer or **c**lient.
**transport**: refers to how the packet is *transported* from a socket to consumer and vice versa. There are several transport Methods
* `n` is for `named pipe` transport better known as a *unix socket* in unix. This is the prefered transport for communication within a single computing device.
* `t` is for `TCP` transport which stands for *transmission control protocol* and is THE transport for IP(Internet Proctocol) networks which means it's great for both LAN and WAN communiction
* `m` is for `MQTT` which is another protocol specifically adopted by the IOT community for easy inter-device communication. It requires one to run a **broker** in order to broker the packets from on device to another and vice versa. Home Assistant is one home control frontend that supports MQTT. This means home assistant can control any device which extends this base.
* `w` is for `web socket`. A *web* socket is a special version of a `TCP` socket that allows easy communication from a browser to a web socket server. This means any classes derviced from this base class will be able to communicate directly with a broswer!
## The UCI JSON Packet 'Protocol'
If you are a javascript programmer then you already know about object hashes. JSON is a `stringified` representation of such object hashes. Being a string JSON can be easily encoded and sent via a socket. It can then be parsed at the other end back into an object hash. There in lies the power of sending a JSON packet rather than some generic string. A UCI JSON packet starts and ends its life as an object hash. When creating this hash you need make sure to include at a minimum at command property (*key:value* pair e.g. `cmd:'switch/on'`). It is this `cmd` property that will result in a function being executed at the other end of the socket using the parsed value of `cmd:` as the function name and any other properties of the packet/hash you include as arguments or data to the called function. The function(s) that can be invoked at the *target* live in a name spaced hash. The base class manages invoking the appropriate function for you. You only need to create the function(s) hash for your instance/class that corresponds to the packet `cmd` values you want to recognize/support. That's the gist of how the UCI socket communication system works. Another cool thing about the UCI protocol is that it attaches automatically a unique header ID to each packet and then *listens* for when a packet comes back with that ID. In this way you can `await` for a response from your socket sever and then take an action like turning a button green after getting confirmation a 'switch/on' command did indeed turn on the corresponding circuit(s). The socket/server modules also support push notifications! As to MQTT the UCI mqtt module takes the `cmd` property and converts it into the equivlent mqtt topic and vice versa sending the remaing hash as the MQTT payload. This makes MQTT interoperabilty with the UCI JSON Packet *Protocol* seamless.
## Getting Started
The best way to get started is to clone this repo and then take a look at and run the four-in-one example found in the /examples. You can run it with `npm run fio` script. This example creates an instance of this base class that fires up four socket/servers of each transport and two consumers of transport tcp and named pipe. With this running in a terminal you can now "mess around" sending packets to this instance with a mqtt or websocket client. Here are two clients I like [mqttbox](http://workswithweb.com/mqttbox.html) and [simple websocket client](https://chrome.google.com/webstore/detail/simple-websocket-client/pfdhoblngboilpfeibdedpjgfnlcodoo?hl=en) which runs only within google chrome . Another options is to use the UCI websocket development client found [here]()
TODO describe how to connect and what packet command/topic to send/use.
## Creating an Instance
## Extending This Base Class
## Options
## API

View File

@ -1,9 +1,10 @@
// communication modules // UCI communication modules
import Socket from '@uci/socket' import Socket from '@uci/socket'
// import Socket from '../../uci-socket/src'
import MQTT from '@uci/mqtt' // requires broker import MQTT from '@uci/mqtt' // requires broker
// import MQTT from '../../uci-mqtt/src/client' // requires broker // import MQTT from '../../uci-mqtt/src/client' // requires broker
import WebSocket from '@uci/websocket' // server only import WebSocket from '@uci/websocket' // server only
// TODO // import WebSocket from '../../uci-websocket/src'
import EventEmitter from 'events' import EventEmitter from 'events'
import pSettle from 'p-settle' import pSettle from 'p-settle'
@ -25,6 +26,10 @@ export default class Base extends EventEmitter {
this._processors = { _default: processor } this._processors = { _default: processor }
this._defaultCmds = commands this._defaultCmds = commands
this._namespaces = namespaces this._namespaces = namespaces
if (opts.useRootNS) { // add root of instance to checking for command functions - not recommended!
this._namespaces.s.push(null)
this._namespaces.c.push(null)
}
this.bindFuncs = bindFuncs this.bindFuncs = bindFuncs
// predefined sockets: // predefined sockets:
// comma delimited list of this form '<name>#<c/p/s>><n=np/t=tcp/m=mqtt/w=web>' // comma delimited list of this form '<name>#<c/p/s>><n=np/t=tcp/m=mqtt/w=web>'
@ -50,20 +55,19 @@ export default class Base extends EventEmitter {
initSockets.push(this.initSocket(name)) initSockets.push(this.initSocket(name))
sockets.push(name) sockets.push(name)
} }
this.started = true
return pSettle(initSockets).then(res=>{ return pSettle(initSockets).then(res=>{
console.log(res) log.info({sockets:res},'response from intializing sockets via instance options')
let err = [] let err = []
res.forEach((p,index) => { res.forEach((p,index) => {
if (p.isRejected) { if (p.isRejected) {
err.push({name:sockets[index],err:p.reason}) err.push({name:sockets[index],err:p.reason})
} }
}) })
this.started = true
return err return err
// TODO if a websocket server was working then push status
// TODO if no mqtt broker then attempt to start one
}) })
// TODO if a websocket server was working then push status
// TODO if no mqtt broker then attempt to start one
} // init } // init
async addSocket (name, type='c', transport='n', options={}) { async addSocket (name, type='c', transport='n', options={}) {
@ -92,7 +96,7 @@ export default class Base extends EventEmitter {
this.socket[name].type = type this.socket[name].type = type
this.socket[name].transport = transport this.socket[name].transport = transport
this.socket[name]._packetProcess = this._packetProcess.bind(this,name) this.socket[name]._packetProcess = this._packetProcess.bind(this,name)
if (this.started) await this.initSocket(name) if (this.started) log.info(await this.initSocket(name))
else return `socket ${name} added` else return `socket ${name} added`
// console.log(name, '====',this.socket[name]) // console.log(name, '====',this.socket[name])
@ -134,6 +138,16 @@ export default class Base extends EventEmitter {
} }
} }
async push(packet) {
let broadcast=[]
for(let name of Object.keys(this.socket)){
if (this.socket[name].type ==='s') {
broadcast.push(this.socket[name].push.bind(this.socket[name]))
}
}
return Promise.all(broadcast.map(push => {return push(packet)}))
}
async sendTransport(packet,transport) { async sendTransport(packet,transport) {
let sends = [] let sends = []
for(let name of Object.keys(this.socket)){ for(let name of Object.keys(this.socket)){
@ -177,8 +191,8 @@ export default class Base extends EventEmitter {
amendSocketProcessing(funcs,trans) { amendSocketProcessing(funcs,trans) {
if (trans) { if (trans) {
if (!this._defaultCmds.c[trans]) this._defaultCmds.c[trans] ={} if (!this._defaultCmds.s[trans]) this._defaultCmds.s[trans] = {}
Object.assign(this._defaultCmds.c[trans],funcs) Object.assign(this._defaultCmds.s[trans],funcs)
} }
Object.assign(this._defaultCmds.c,funcs) Object.assign(this._defaultCmds.c,funcs)
} }
@ -194,10 +208,10 @@ export default class Base extends EventEmitter {
} }
} }
// takes and returns a packet // func should take and return a packet
beforeSendHook (type,funcs){} // TODO before packet send // beforeSendHook (func,type,transport){} // TODO
afterReceiveHook(type,funcs){} // TODO after receiv // afterReceiveHook(func,type,transport){} // TODO
afterProcessHook(type,funcs){} // TODO // afterProcessHook(func,type,transport){} // TODO
// here you can add namespaced functions for packet commands // here you can add namespaced functions for packet commands
consumersProcessor(func) { consumersProcessor(func) {
@ -226,10 +240,6 @@ export default class Base extends EventEmitter {
else return this._namespaces[type].unshift(space) else return this._namespaces[type].unshift(space)
} }
// registerPacketProcessor(name,func) {
// this._packetProcess = func
// }
/* /*
* *

View File

@ -32,14 +32,15 @@ const process = {
} }
const namespaces = { const namespaces = {
s: ['s',null,'_defaultCmds.s'], s: ['s','_defaultCmds.s'],
c: ['c',null,'_defaultCmds.c'], c: ['c','_defaultCmds.c'],
cn: ['cn'], cn: ['cn'],
ct: ['ct'], ct: ['ct'],
sn: ['sn'], sn: ['sn'],
st: ['st'], st: ['st'],
cm: ['cm'], cm: ['cm'],
sm: ['sm'], sm: ['sm'],
sw: ['sw'],
} }
/* /*