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 socketfuncs = {
write: {
happy: function(packet){
switch: {
on: function(packet){
return new Promise( async (resolve) => {
let res = {}
res.response=packet.data+ ' and Im a happy puppy :)'
packet.cmd='status/on'
this.push(packet) // push to all active socket servers
let res = { response:'status pushed on to all clients'}
return resolve(res)
})
},
sad: function(packet){
off: function(packet){
return new Promise( async (resolve) => {
let res = {}
res.response='Im a sad dog :('
return resolve(res)
})
}
}
}
const tcpfuncs = {
write: {
happy: function(packet){
return new Promise( async (resolve) => {
let res = {}
res.cmd='reply'
res.response=packet.data+ ' and Im a TCP happy puppy :)'
packet.cmd='status/off'
this.push(packet) // push to all active socket servers
let res = { response:'status pushed off to all clients'}
return resolve(res)
})
},
sad: function(packet){
toggle: function(packet){
return new Promise( async (resolve) => {
let res = {}
res.cmd='reply'
res.response='Im a TCP sad dog :('
// 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)
})
}
}
}
const clientfuncs = {
reply: packet => {
return new Promise( async (resolve) => {
console.log('==============Reply Processor at Consumer==========')
console.dir(packet.response)
return resolve()
})
},
status: {
on: packet => {
return new Promise( async (resolve) => {
console.log('==============Pushed to Consumer==========')
console.log(`Switch ${packet.id} is on`)
return resolve()
})
},
off: packet => {
return new Promise( async (resolve) => {
console.log('==============Pushed to Consumer==========')
console.log(`Switch ${packet.id} is off`)
return resolve()
})
}
}
}
// const delay = time => new Promise(res=>setTimeout(()=>res(),time))
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'})
fio.s = socketfuncs
fio.c = clientfuncs
;
(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'}))
{
const mqttProcess = async function (packet,topic) {
console.log('==============mqtt incoming packet/topic processor =========')
console.log(packet, topic, fio.s[topic][packet.cmd])
if (packet.cmd) console.log(await fio.s[topic][packet.cmd](packet))
console.log('===========================')
}
let res = await fio.init()
console.log('initialize errors',res)
console.log('waiting for packets')
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 = {}
console.log('=============sending============')
packet = {cmd:'echo', data:'some data to echo'}
console.log(packet)
await fio.send(packet,'uc')
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)
packet = {cmd:'switch/on', id:'1'}
console.log('sending to socket uc',packet)
await fio.send('uc',packet)
// process.kill(process.pid, 'SIGTERM')
})().catch(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'
let dy = new Base({id:'dynamic'})
let dy = new Base({id:'websocket'})
dy.switch = {
on: function(packet){
@ -18,13 +18,15 @@ dy.switch = {
},
off: function(packet){
return new Promise( async (resolve) => {
console.log(packet)
console.log(`turning switch off for id ${packet.id||packet.data}`)
// call switch on here
let res = {}
res.cmd='switch/status'
res.status='off'
res.id = packet.id
console.log('broadcast',res)
this.push(res)
res = { cmd:'ack'}
return resolve(res)
})
}
@ -35,8 +37,7 @@ dy.switch = {
(async () => {
await dy.init()
console.log('started', dy.started)
// await Promise.all([dy.addSocket('mqc','c','m'),dy.addSocket('mqs','s','m')])
await dy.addSocket('web','s','w')
await dy.addSocket('mqs','s','m')
dy.socket.mqs.subscribe(['switch/on','switch/off','switch/toggle'])

View File

@ -1,7 +1,7 @@
{
"name": "@uci/base",
"version": "0.1.4",
"description": "Mutli Level/Transport Message/Event Classes",
"version": "0.1.6",
"description": "Multi type and transport JSON packet communication base class. Used in UCI extended classes",
"main": "src/base",
"scripts": {
"deve": "./node_modules/.bin/nodemon -r esm examples/four-in-one",
@ -33,17 +33,17 @@
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"codecov": "^3.0.0",
"esm": "^3.0.36",
"esm": "^3.0.37",
"istanbul": "^0.4.5",
"mocha": "^5.2.0",
"nodemon": "^1.14.12"
},
"dependencies": {
"@uci/logger": "^0.0.3",
"@uci/mqtt": "0.0.9",
"@uci/socket": "^0.1.1",
"@uci/mqtt": "^0.1.0",
"@uci/socket": "^0.1.4",
"@uci/utils": "^0.1.1",
"@uci/websocket": "^0.1.8",
"@uci/websocket": "^0.1.9",
"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 -->
[![Build Status](https://img.shields.io/travis/uCOMmandIt/uci-pkg-template.svg?branch=master)](https://travis-ci.org/uCOMmandIt/uci-pkg-template)
[![Inline docs](http://inch-ci.org/github/uCOMmandIt/uci-pkg-template.svg?branch=master)](http://inch-ci.org/github/uCOMmandIt/uci-pkg-template)
[![Dependencies](https://img.shields.io/david/uCOMmandIt/uci-pkg-template.svg)](https://david-dm.org/uCOMmandIt/uci-pkg-template)
[![devDependencies](https://img.shields.io/david/dev/uCOMmandIt/uci-pkg-template.svg)](https://david-dm.org/uCOMmandIt/uci-pkg-template?type=dev)
[![codecov](https://img.shields.io/codecov/c/github/uCOMmandIt/uci-pkg-template/master.svg)](https://codecov.io/gh/uCOMmandIt/uci-pkg-template)
Base Class extended for all UCI Classes
[![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-base.svg?branch=master)](http://inch-ci.org/github/uCOMmandIt/uci-base)
[![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-base.svg)](https://david-dm.org/uCOMmandIt/uci-base?type=dev)
[![codecov](https://img.shields.io/codecov/c/github/uCOMmandIt/uci-base/master.svg)](https://codecov.io/gh/uCOMmandIt/uci-base)
changed sockets options now can add as many as you want of either type and transport
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)
default processing splits into socket or consumer
default processing has calls looking for packet cmd functions before returning error packet
1. Added Namespace // TODO
2. A Pariticular Sockect
3. Root of instance
4. The socket type and transport //TODO
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.
## What is it
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.
## What's it good for
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)
## Prerequisites
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/src'
import MQTT from '@uci/mqtt' // requires broker
// import MQTT from '../../uci-mqtt/src/client' // requires broker
import WebSocket from '@uci/websocket' // server only
// TODO
// import WebSocket from '../../uci-websocket/src'
import EventEmitter from 'events'
import pSettle from 'p-settle'
@ -25,6 +26,10 @@ export default class Base extends EventEmitter {
this._processors = { _default: processor }
this._defaultCmds = commands
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
// predefined sockets:
// 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))
sockets.push(name)
}
this.started = true
return pSettle(initSockets).then(res=>{
console.log(res)
log.info({sockets:res},'response from intializing sockets via instance options')
let err = []
res.forEach((p,index) => {
if (p.isRejected) {
err.push({name:sockets[index],err:p.reason})
}
})
this.started = true
return err
})
// TODO if a websocket server was working then push status
// TODO if no mqtt broker then attempt to start one
})
} // init
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].transport = transport
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`
// 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) {
let sends = []
for(let name of Object.keys(this.socket)){
@ -177,8 +191,8 @@ export default class Base extends EventEmitter {
amendSocketProcessing(funcs,trans) {
if (trans) {
if (!this._defaultCmds.c[trans]) this._defaultCmds.c[trans] ={}
Object.assign(this._defaultCmds.c[trans],funcs)
if (!this._defaultCmds.s[trans]) this._defaultCmds.s[trans] = {}
Object.assign(this._defaultCmds.s[trans],funcs)
}
Object.assign(this._defaultCmds.c,funcs)
}
@ -194,10 +208,10 @@ export default class Base extends EventEmitter {
}
}
// takes and returns a packet
beforeSendHook (type,funcs){} // TODO before packet send
afterReceiveHook(type,funcs){} // TODO after receiv
afterProcessHook(type,funcs){} // TODO
// func should take and return a packet
// beforeSendHook (func,type,transport){} // TODO
// afterReceiveHook(func,type,transport){} // TODO
// afterProcessHook(func,type,transport){} // TODO
// here you can add namespaced functions for packet commands
consumersProcessor(func) {
@ -226,10 +240,6 @@ export default class Base extends EventEmitter {
else return this._namespaces[type].unshift(space)
}
// registerPacketProcessor(name,func) {
// this._packetProcess = func
// }
/*
*

View File

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