static void jswrap_waveform_start(JsVar *waveform, Pin pin, JsVarFloat freq, JsVar *options, bool isWriting) { bool running = jsvGetBoolAndUnLock(jsvObjectGetChild(waveform, "running", 0)); if (running) { jsExceptionHere(JSET_ERROR, "Waveform is already running"); return; } if (!jshIsPinValid(pin)) { jsExceptionHere(JSET_ERROR, "Invalid pin"); return; } if (!isfinite(freq) || freq<0.001) { jsExceptionHere(JSET_ERROR, "Frequency must be above 0.001Hz"); return; } JsSysTime startTime = jshGetSystemTime(); bool repeat = false; if (jsvIsObject(options)) { JsVarFloat t = jsvGetFloatAndUnLock(jsvObjectGetChild(options, "time", 0)); if (isfinite(t) && t>0) startTime = jshGetTimeFromMilliseconds(t*1000); repeat = jsvGetBoolAndUnLock(jsvObjectGetChild(options, "repeat", 0)); } else if (!jsvIsUndefined(options)) { jsExceptionHere(JSET_ERROR, "Expecting options to be undefined or an Object, not %t", options); } bool is16Bit = false; JsVar *buffer = jswrap_waveform_getBuffer(waveform,0, &is16Bit); JsVar *buffer2 = jswrap_waveform_getBuffer(waveform,1,0); UtilTimerEventType eventType; if (is16Bit) { eventType = isWriting ? UET_WRITE_SHORT : UET_READ_SHORT; } else { eventType = isWriting ? UET_WRITE_BYTE : UET_READ_BYTE; } // And finally set it up if (!jstStartSignal(startTime, jshGetTimeFromMilliseconds(1000.0 / freq), pin, buffer, repeat?(buffer2?buffer2:buffer):0, eventType)) jsWarn("Unable to schedule a timer"); jsvUnLock(buffer); jsvUnLock(buffer2); jsvUnLock(jsvObjectSetChild(waveform, "running", jsvNewFromBool(true))); jsvUnLock(jsvObjectSetChild(waveform, "freq", jsvNewFromFloat(freq))); // Add to our list of active waveforms JsVar *waveforms = jsvObjectGetChild(execInfo.hiddenRoot, JSI_WAVEFORM_NAME, JSV_ARRAY); if (waveforms) { jsvArrayPush(waveforms, waveform); jsvUnLock(waveforms); } }
/*JSON{ "type" : "function", "name" : "analogWrite", "generate" : "jswrap_io_analogWrite", "params" : [ ["pin","pin",["The pin to use","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `PWM` or `DAC` markers."]], ["value","float","A value between 0 and 1"], ["options","JsVar",["An object containing options for analog output - see below"]] ] } Set the analog Value of a pin. It will be output using PWM. Objects can contain: * `freq` - pulse frequency in Hz, eg. ```analogWrite(A0,0.5,{ freq : 10 });``` - specifying a frequency will force PWM output, even if the pin has a DAC * `soft` - boolean, If true software PWM is used if available. * `forceSoft` - boolean, If true software PWM is used even **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"output"` */ void jswrap_io_analogWrite(Pin pin, JsVarFloat value, JsVar *options) { JsVarFloat freq = 0; JshAnalogOutputFlags flags = JSAOF_NONE; if (jsvIsObject(options)) { freq = jsvGetFloatAndUnLock(jsvObjectGetChild(options, "freq", 0)); if (jsvGetBoolAndUnLock(jsvObjectGetChild(options, "forceSoft", 0))) flags |= JSAOF_FORCE_SOFTWARE; else if (jsvGetBoolAndUnLock(jsvObjectGetChild(options, "soft", 0))) flags |= JSAOF_ALLOW_SOFTWARE; } jshPinAnalogOutput(pin, value, freq, flags); }
// returns 0 on success and a (negative) error number on failure int socketSendData(JsNetwork *net, JsVar *connection, int sckt, JsVar **sendData) { char *buf = alloca(net->chunkSize); // allocate on stack assert(!jsvIsEmptyString(*sendData)); size_t bufLen = httpStringGet(*sendData, buf, net->chunkSize); int num = netSend(net, sckt, buf, bufLen); if (num < 0) return num; // an error occurred // Now cut what we managed to send off the beginning of sendData if (num > 0) { JsVar *newSendData = 0; if (num < (int)jsvGetStringLength(*sendData)) { // we didn't send all of it... cut out what we did send newSendData = jsvNewFromStringVar(*sendData, (size_t)num, JSVAPPENDSTRINGVAR_MAXLENGTH); } else { // we sent all of it! Issue a drain event, unless we want to close, then we shouldn't // callback for more data bool wantClose = jsvGetBoolAndUnLock(jsvObjectGetChild(connection,HTTP_NAME_CLOSE,0)); if (!wantClose) { jsiQueueObjectCallbacks(connection, HTTP_NAME_ON_DRAIN, &connection, 1); } newSendData = jsvNewFromEmptyString(); } jsvUnLock(*sendData); *sendData = newSendData; } return 0; }
/*JSON{ "type" : "staticmethod", "class" : "fs", "name" : "pipe", "ifndef" : "SAVE_ON_FLASH", "generate" : "jswrap_pipe", "params" : [ ["source","JsVar","The source file/stream that will send content."], ["destination","JsVar","The destination file/stream that will receive content from the source."], ["options","JsVar",["An optional object `{ chunkSize : int=64, end : bool=true, complete : function }`","chunkSize : The amount of data to pipe from source to destination at a time","complete : a function to call when the pipe activity is complete","end : call the 'end' function on the destination when the source is finished"]] ] }*/ void jswrap_pipe(JsVar* source, JsVar* dest, JsVar* options) { if (!source || !dest) return; JsVar *pipe = jspNewObject(0, "Pipe"); JsVar *arr = pipeGetArray(true); JsVar* position = jsvNewFromInteger(0); if (pipe && arr && position) {// out of memory? JsVar *readFunc = jspGetNamedField(source, "read", false); JsVar *writeFunc = jspGetNamedField(dest, "write", false); if(jsvIsFunction(readFunc)) { if(jsvIsFunction(writeFunc)) { JsVarInt chunkSize = 64; bool callEnd = true; // parse Options Object if (jsvIsObject(options)) { JsVar *c; c = jsvObjectGetChild(options, "complete", false); if (c) { jsvObjectSetChild(pipe, "#oncomplete", c); jsvUnLock(c); } c = jsvObjectGetChild(options, "end", false); if (c) callEnd = jsvGetBoolAndUnLock(c); c = jsvObjectGetChild(options, "chunkSize", false); if (c) { if (jsvIsNumeric(c) && jsvGetInteger(c)>0) chunkSize = jsvGetInteger(c); else jsWarn("chunkSize must be an integer > 0"); jsvUnLock(c); } } else if (!jsvIsUndefined(options)) { jsWarn("'options' must be an object, or undefined"); } // set up our event listeners jswrap_object_addEventListener(source, "close", jswrap_pipe_src_close_listener, JSWAT_VOID | (JSWAT_JSVAR << (JSWAT_BITS*1))); jswrap_object_addEventListener(dest, "drain", jswrap_pipe_drain_listener, JSWAT_VOID | (JSWAT_JSVAR << (JSWAT_BITS*1))); jswrap_object_addEventListener(dest, "close", jswrap_pipe_dst_close_listener, JSWAT_VOID | (JSWAT_JSVAR << (JSWAT_BITS*1))); // set up the rest of the pipe jsvUnLock(jsvObjectSetChild(pipe, "chunkSize", jsvNewFromInteger(chunkSize))); jsvUnLock(jsvObjectSetChild(pipe, "end", jsvNewFromBool(callEnd))); jsvUnLock(jsvAddNamedChild(pipe, position, "position")); jsvUnLock(jsvAddNamedChild(pipe, source, "source")); jsvUnLock(jsvAddNamedChild(pipe, dest, "destination")); // add the pipe to our list jsvArrayPush(arr, pipe); } else { jsExceptionHere(JSET_ERROR, "Destination object does not implement the required write(buffer, length, position) method."); } } else { jsExceptionHere(JSET_ERROR, "Source object does not implement the required read(buffer, length, position) method."); } jsvUnLock(readFunc); jsvUnLock(writeFunc); } jsvUnLock(arr); jsvUnLock(pipe); jsvUnLock(position); }
static NO_INLINE int i2c_get_address(JsVar *address, bool *sendStop) { *sendStop = true; if (jsvIsObject(address)) { JsVar *stopVar = jsvObjectGetChild(address, "stop", 0); if (stopVar) *sendStop = jsvGetBoolAndUnLock(stopVar); return jsvGetIntegerAndUnLock(jsvObjectGetChild(address, "address", 0)); } else return jsvGetInteger(address); }
/*JSON{ "type":"idle", "generate" : "jswrap_waveform_idle", "ifndef" : "SAVE_ON_FLASH" }*/ bool jswrap_waveform_idle() { JsVar *waveforms = jsvObjectGetChild(execInfo.hiddenRoot, JSI_WAVEFORM_NAME, 0); if (waveforms) { JsvArrayIterator it; jsvArrayIteratorNew(&it, waveforms); while (jsvArrayIteratorHasElement(&it)) { JsVar *waveform = jsvArrayIteratorGetElement(&it); bool running = jsvGetBoolAndUnLock(jsvObjectGetChild(waveform, "running", 0)); if (running) { JsVar *buffer = jswrap_waveform_getBuffer(waveform,0,0); UtilTimerTask task; // Search for a timer task if (!jstGetLastBufferTimerTask(buffer, &task)) { // if the timer task is now gone... JsVar *arrayBuffer = jsvObjectGetChild(waveform, "buffer", 0); jsiQueueObjectCallbacks(waveform, "#onfinish", &arrayBuffer, 1); jsvUnLock(arrayBuffer); running = false; jsvUnLock(jsvObjectSetChild(waveform, "running", jsvNewFromBool(running))); } else { // If the timer task is still there... if (task.data.buffer.nextBuffer && task.data.buffer.nextBuffer != task.data.buffer.currentBuffer) { // if it is a double-buffered task int currentBuffer = (jsvGetRef(buffer)==task.data.buffer.currentBuffer) ? 0 : 1; JsVar *oldBuffer = jsvObjectGetChild(waveform, "currentBuffer", JSV_INTEGER); if (jsvGetInteger(oldBuffer) !=currentBuffer) { // buffers have changed - fire off a 'buffer' event with the buffer that needs to be filled jsvSetInteger(oldBuffer, currentBuffer); JsVar *arrayBuffer = jsvObjectGetChild(waveform, (currentBuffer==0) ? "buffer" : "buffer2", 0); jsiQueueObjectCallbacks(waveform, "#onbuffer", &arrayBuffer, 1); jsvUnLock(arrayBuffer); } jsvUnLock(oldBuffer); } } jsvUnLock(buffer); } jsvUnLock(waveform); // if not running, remove waveform from this list if (!running) jsvArrayIteratorRemoveAndGotoNext(&it, waveforms); else jsvArrayIteratorNext(&it); } jsvArrayIteratorFree(&it); jsvUnLock(waveforms); } return false; // no need to stay awake - an IRQ will wake us }
/*JSON{ "type":"method", "class": "Waveform", "name" : "stop", "ifndef" : "SAVE_ON_FLASH", "description" : "Stop a waveform that is currently outputting", "generate" : "jswrap_waveform_stop" }*/ void jswrap_waveform_stop(JsVar *waveform) { bool running = jsvGetBoolAndUnLock(jsvObjectGetChild(waveform, "running", 0)); if (!running) { jsExceptionHere(JSET_ERROR, "Waveform is not running"); return; } JsVar *buffer = jswrap_waveform_getBuffer(waveform,0,0); if (!jstStopBufferTimerTask(buffer)) { jsExceptionHere(JSET_ERROR, "Waveform couldn't be stopped"); } jsvUnLock(buffer); // now run idle loop as this will issue the finish event and will clean up jswrap_waveform_idle(); }
static bool handlePipe(JsVar *arr, JsvObjectIterator *it, JsVar* pipe) { bool paused = jsvGetBoolAndUnLock(jsvObjectGetChild(pipe,"drainWait",0)); if (paused) return false; JsVar *position = jsvObjectGetChild(pipe,"position",0); JsVar *chunkSize = jsvObjectGetChild(pipe,"chunkSize",0); JsVar *source = jsvObjectGetChild(pipe,"source",0); JsVar *destination = jsvObjectGetChild(pipe,"destination",0); bool dataTransferred = false; if(source && destination && chunkSize && position) { JsVar *readFunc = jspGetNamedField(source, "read", false); JsVar *writeFunc = jspGetNamedField(destination, "write", false); if (jsvIsFunction(readFunc) && jsvIsFunction(writeFunc)) { // do the objects have the necessary methods on them? JsVar *buffer = jspExecuteFunction(readFunc, source, 1, &chunkSize); if(buffer) { JsVarInt bufferSize = jsvGetLength(buffer); if (bufferSize>0) { JsVar *response = jspExecuteFunction(writeFunc, destination, 1, &buffer); if (jsvIsBoolean(response) && jsvGetBool(response)==false) { // If boolean false was returned, wait for drain event (http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback) jsvUnLock(jsvObjectSetChild(pipe,"drainWait",jsvNewFromBool(true))); } jsvUnLock(response); jsvSetInteger(position, jsvGetInteger(position) + bufferSize); } jsvUnLock(buffer); dataTransferred = true; // so we don't close the pipe if we get an empty string } } else { if(!jsvIsFunction(readFunc)) jsExceptionHere(JSET_ERROR, "Source Stream does not implement the required read(length) method."); if(!jsvIsFunction(writeFunc)) jsExceptionHere(JSET_ERROR, "Destination Stream does not implement the required write(buffer) method."); } jsvUnLock(readFunc); jsvUnLock(writeFunc); } if(!dataTransferred) { // when no more chunks are possible, execute the callback handlePipeClose(arr, it, pipe); } jsvUnLock(source); jsvUnLock(destination); jsvUnLock(chunkSize); jsvUnLock(position); return dataTransferred; }
static void handlePipeClose(JsVar *arr, JsvObjectIterator *it, JsVar* pipe) { jsiQueueObjectCallbacks(pipe, "#oncomplete", &pipe, 1); // also call 'end' if 'end' was passed as an initialisation option if (jsvGetBoolAndUnLock(jsvObjectGetChild(pipe,"end",0))) { // call destination.end if available JsVar *destination = jsvObjectGetChild(pipe,"destination",0); if (destination) { // remove our drain and close listeners. // TODO: This removes ALL listeners. Maybe we should just remove ours? jswrap_object_removeAllListeners_cstr(destination, "drain"); jswrap_object_removeAllListeners_cstr(destination, "close"); // execute the 'end' function JsVar *endFunc = jspGetNamedField(destination, "end", false); if (endFunc) { jsvUnLock(jspExecuteFunction(endFunc, destination, 0, 0)); jsvUnLock(endFunc); } // execute the 'close' function JsVar *closeFunc = jspGetNamedField(destination, "close", false); if (closeFunc) { jsvUnLock(jspExecuteFunction(closeFunc, destination, 0, 0)); jsvUnLock(closeFunc); } jsvUnLock(destination); } /* call source.close if available - probably not what node does but seems very sensible in this case. If you don't want it, set end:false */ JsVar *source = jsvObjectGetChild(pipe,"source",0); if (source) { // TODO: This removes ALL listeners. Maybe we should just remove ours? jswrap_object_removeAllListeners_cstr(source, "close"); // execute the 'close' function JsVar *closeFunc = jspGetNamedField(source, "close", false); if (closeFunc) { jsvUnLock(jspExecuteFunction(closeFunc, source, 0, 0)); jsvUnLock(closeFunc); } } jsvUnLock(source); } JsVar *idx = jsvObjectIteratorGetKey(it); jsvRemoveChild(arr,idx); jsvUnLock(idx); }
/*JSON{ "type":"constructor", "class": "Waveform", "name": "Waveform", "ifndef" : "SAVE_ON_FLASH", "description" : [ "Create a waveform class. This allows high speed input and output of waveforms. It has an internal variable called `buffer` (as well as `buffer2` when double-buffered - see `options` below) which contains the data to input/output.", "When double-buffered, a 'buffer' event will be emitted each time a buffer is finished with (the argument is that buffer). When the recording stops, a 'finish' event will be emitted (with the first argument as the buffer)." ], "generate" : "jswrap_waveform_constructor", "params" : [ [ "samples", "int32", "The number of samples" ], [ "options", "JsVar", "Optional options struct `{doubleBuffer:bool, bits : 8/16}` where: `doubleBuffer` is whether to allocate two buffers or not (default false), and bits is the amount of bits to use (default 8)." ] ], "return" : [ "JsVar", "An Waveform object" ] }*/ JsVar *jswrap_waveform_constructor(int samples, JsVar *options) { if (samples<=0) { jsExceptionHere(JSET_ERROR, "Samples must be greater than 0"); return 0; } bool doubleBuffer = false; bool use16bit = false; if (jsvIsObject(options)) { doubleBuffer = jsvGetBoolAndUnLock(jsvObjectGetChild(options, "doubleBuffer", 0)); int bits = (int)jsvGetIntegerAndUnLock(jsvObjectGetChild(options, "bits", 0)); if (bits!=0 && bits!=8 && bits!=16) { jsExceptionHere(JSET_ERROR, "Invalid number of bits"); return 0; } else if (bits==16) use16bit = true; } else if (!jsvIsUndefined(options)) { jsExceptionHere(JSET_ERROR, "Expecting options to be undefined or an Object, not %t", options); } JsVar *arrayLength = jsvNewFromInteger(samples); JsVarDataArrayBufferViewType bufferType = use16bit ? ARRAYBUFFERVIEW_UINT16 : ARRAYBUFFERVIEW_UINT8; JsVar *arrayBuffer = jswrap_typedarray_constructor(bufferType, arrayLength, 0, 0); JsVar *arrayBuffer2 = 0; if (doubleBuffer) arrayBuffer2 = jswrap_typedarray_constructor(bufferType, arrayLength, 0, 0); jsvUnLock(arrayLength); JsVar *waveform = jspNewObject(0, "Waveform"); if (!waveform || !arrayBuffer || (doubleBuffer && !arrayBuffer2)) { jsvUnLock(waveform); jsvUnLock(arrayBuffer); // out of memory jsvUnLock(arrayBuffer2); return 0; } jsvUnLock(jsvObjectSetChild(waveform, "buffer", arrayBuffer)); if (arrayBuffer2) jsvUnLock(jsvObjectSetChild(waveform, "buffer2", arrayBuffer2)); return waveform; }
// 'end' this connection void clientRequestEnd(JsNetwork *net, JsVar *httpClientReqVar) { SocketType socketType = socketGetType(httpClientReqVar); if ((socketType&ST_TYPE_MASK) == ST_HTTP) { JsVar *finalData = 0; if (jsvGetBoolAndUnLock(jsvObjectGetChild(httpClientReqVar, HTTP_NAME_CHUNKED, 0))) { // If we were asked to send 'chunked' data, we need to finish up finalData = jsvNewFromString(""); } // on HTTP, this actually means we connect // force sendData to be made clientRequestWrite(net, httpClientReqVar, finalData); jsvUnLock(finalData); } else { // on normal sockets, we actually request close after all data sent jsvObjectSetChildAndUnLock(httpClientReqVar, HTTP_NAME_CLOSE, jsvNewFromBool(true)); // if we never sent any data, make sure we close 'now' JsVar *sendData = jsvObjectGetChild(httpClientReqVar, HTTP_NAME_SEND_DATA, 0); if (!sendData || jsvIsEmptyString(sendData)) jsvObjectSetChildAndUnLock(httpClientReqVar, HTTP_NAME_CLOSENOW, jsvNewFromBool(true)); jsvUnLock(sendData); } }
/*JSON{ "type":"kill", "generate" : "jswrap_waveform_kill", "ifndef" : "SAVE_ON_FLASH" }*/ void jswrap_waveform_kill() { // be sure to remove all waveforms... JsVar *waveforms = jsvObjectGetChild(execInfo.hiddenRoot, JSI_WAVEFORM_NAME, 0); if (waveforms) { JsvArrayIterator it; jsvArrayIteratorNew(&it, waveforms); while (jsvArrayIteratorHasElement(&it)) { JsVar *waveform = jsvArrayIteratorGetElement(&it); bool running = jsvGetBoolAndUnLock(jsvObjectGetChild(waveform, "running", 0)); if (running) { JsVar *buffer = jswrap_waveform_getBuffer(waveform,0,0); if (!jstStopBufferTimerTask(buffer)) { jsExceptionHere(JSET_ERROR, "Waveform couldn't be stopped"); } jsvUnLock(buffer); } jsvUnLock(waveform); // if not running, remove waveform from this list jsvArrayIteratorRemoveAndGotoNext(&it, waveforms); } jsvArrayIteratorFree(&it); jsvUnLock(waveforms); } }
/*JSON{ "type" : "method", "class" : "WLAN", "name" : "connect", "generate" : "jswrap_wlan_connect", "params" : [ ["ap","JsVar","Access point name"], ["key","JsVar","WPA2 key (or undefined for unsecured connection)"], ["callback","JsVar","Function to call back with connection status. It has one argument which is one of 'connect'/'disconnect'/'dhcp'"] ], "return" : ["bool","True if connection succeeded, false if it didn't."] } Connect to a wireless network */ bool jswrap_wlan_connect(JsVar *wlanObj, JsVar *vAP, JsVar *vKey, JsVar *callback) { if (!(jsvIsUndefined(callback) || jsvIsFunction(callback))) { jsError("Expecting callback Function but got %t", callback); return 0; } JsNetwork net; if (!networkGetFromVar(&net)) return false; // if previously completely disconnected, try and reconnect if (jsvGetBoolAndUnLock(jsvObjectGetChild(wlanObj,JS_HIDDEN_CHAR_STR"DIS",0))) { cc3000_initialise(wlanObj); jsvObjectSetChildAndUnLock(wlanObj,JS_HIDDEN_CHAR_STR"DIS", jsvNewFromBool(false)); } if (jsvIsFunction(callback)) { jsvObjectSetChild(wlanObj, CC3000_ON_STATE_CHANGE, callback); } jsvObjectSetChild(wlanObj,JS_HIDDEN_CHAR_STR"AP", vAP); // no unlock intended jsvObjectSetChild(wlanObj,JS_HIDDEN_CHAR_STR"KEY", vKey); // no unlock intended char ap[32]; char key[32]; unsigned long security = WLAN_SEC_UNSEC; jsvGetString(vAP, ap, sizeof(ap)); if (jsvIsString(vKey)) { security = WLAN_SEC_WPA2; jsvGetString(vKey, key, sizeof(key)); } // might want to set wlan_ioctl_set_connection_policy bool connected = wlan_connect(security, ap, (long)strlen(ap), NULL, (unsigned char*)key, (long)strlen(key))==0; networkFree(&net); // note that we're only online (for networkState) when DHCP succeeds return connected; }
/*JSON{ "type" : "staticmethod", "class" : "NRF", "name" : "setServices", "generate" : "jswrap_nrf_bluetooth_setServices", "params" : [ ["data","JsVar","The service (and characteristics) to advertise"] ] } BETA: This only partially works at the moment Change the services and characteristics Espruino advertises. ``` NRF.setServices({ 0xBCDE : { 0xABCD : { value : "Hello", // optional maxLen : 5, // optional (otherwise is length of initial value) broadcast : false, // optional, default is false readable : true, // optional, default is false writable : true, // optional, default is false onWrite : function(evt) { // optional console.log("Got ", evt.data); } } // more characteristics allowed } // more services allowed }); ``` */ void jswrap_nrf_bluetooth_setServices(JsVar *data) { uint32_t err_code; // TODO: Reset services if (jsvIsObject(data)) { JsvObjectIterator it; jsvObjectIteratorNew(&it, data); while (jsvObjectIteratorHasValue(&it)) { ble_uuid_t ble_uuid; uint16_t service_handle; // Add the service BLE_UUID_BLE_ASSIGN(ble_uuid, jsvGetIntegerAndUnLock(jsvObjectIteratorGetKey(&it))); err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &service_handle); if (err_code) { jsExceptionHere(JSET_ERROR, "Got BLE error code %d in gatts_service_add", err_code); break; } // sd_ble_gatts_include_add ? // Now add characteristics JsVar *serviceVar = jsvObjectIteratorGetValue(&it); JsvObjectIterator serviceit; jsvObjectIteratorNew(&serviceit, serviceVar); while (jsvObjectIteratorHasValue(&serviceit)) { ble_uuid_t char_uuid; ble_gatts_char_md_t char_md; ble_gatts_attr_t attr_char_value; ble_gatts_attr_md_t attr_md; ble_gatts_char_handles_t characteristic_handles; BLE_UUID_BLE_ASSIGN(char_uuid, jsvGetIntegerAndUnLock(jsvObjectIteratorGetKey(&serviceit))); JsVar *charVar = jsvObjectIteratorGetValue(&serviceit); memset(&char_md, 0, sizeof(char_md)); if (jsvGetBoolAndUnLock(jsvObjectGetChild(charVar, "broadcast", 0))) char_md.char_props.broadcast = 1; if (jsvGetBoolAndUnLock(jsvObjectGetChild(charVar, "readable", 0))) char_md.char_props.read = 1; if (jsvGetBoolAndUnLock(jsvObjectGetChild(charVar, "writable", 0))) { char_md.char_props.write = 1; char_md.char_props.write_wo_resp = 1; } char_md.p_char_user_desc = NULL; char_md.p_char_pf = NULL; char_md.p_user_desc_md = NULL; char_md.p_cccd_md = NULL; char_md.p_sccd_md = NULL; memset(&attr_md, 0, sizeof(attr_md)); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm); attr_md.vloc = BLE_GATTS_VLOC_STACK; attr_md.rd_auth = 0; attr_md.wr_auth = 0; attr_md.vlen = 1; // TODO: variable length? memset(&attr_char_value, 0, sizeof(attr_char_value)); attr_char_value.p_uuid = &char_uuid; attr_char_value.p_attr_md = &attr_md; attr_char_value.init_len = 0; attr_char_value.init_offs = 0; attr_char_value.p_value = 0; attr_char_value.max_len = (uint16_t)jsvGetIntegerAndUnLock(jsvObjectGetChild(charVar, "maxLen", 0)); if (attr_char_value.max_len==0) attr_char_value.max_len=1; // get initial data JsVar *charValue = jsvObjectGetChild(charVar, "value", 0); if (charValue) { JSV_GET_AS_CHAR_ARRAY(vPtr, vLen, charValue); if (vPtr && vLen) { attr_char_value.p_value = (uint8_t*)vPtr; attr_char_value.init_len = vLen; if (attr_char_value.init_len > attr_char_value.max_len) attr_char_value.max_len = attr_char_value.init_len; } } err_code = sd_ble_gatts_characteristic_add(service_handle, &char_md, &attr_char_value, &characteristic_handles); jsvUnLock(charValue); // unlock here in case we were storing data in a flat string if (err_code) { jsExceptionHere(JSET_ERROR, "Got BLE error code %d in gatts_characteristic_add", err_code); break; } // Add Write callback JsVar *writeCb = jsvObjectGetChild(charVar, "onWrite", 0); if (writeCb) { char eventName[12]; ble_handle_to_write_event_name(eventName, characteristic_handles.value_handle); jsvObjectSetChildAndUnLock(execInfo.root, eventName, writeCb); } jsvUnLock(charVar); /* We'd update the characteristic with: memset(&hvx_params, 0, sizeof(hvx_params)); hvx_params.handle = characteristic_handle.value_handle; hvx_params.p_data = p_string; hvx_params.p_len = &length; hvx_params.type = BLE_GATT_HVX_NOTIFICATION; return sd_ble_gatts_hvx(p_nus->conn_handle, &hvx_params); Maybe we could find the handle out based on characteristic UUID, rather than having to store it? */ jsvObjectIteratorNext(&serviceit); } jsvObjectIteratorFree(&serviceit); jsvUnLock(serviceVar); jsvObjectIteratorNext(&it); } jsvObjectIteratorFree(&it); } else if (!jsvIsUndefined(data)) { jsExceptionHere(JSET_TYPEERROR, "Expecting object or undefined, got %t", data); } }
void httpClientConnectionsIdle() { char buf[64]; JsVar *arr = httpGetArray(HTTP_ARRAY_HTTP_CLIENT_CONNECTIONS,false); if (!arr) return; JsArrayIterator it; jsvArrayIteratorNew(&it, arr); while (jsvArrayIteratorHasElement(&it)) { JsVar *connection = jsvArrayIteratorGetElement(&it); bool closeConnectionNow = jsvGetBoolAndUnLock(jsvObjectGetChild(connection, HTTP_NAME_CLOSENOW, false)); SOCKET sckt = (SOCKET)jsvGetIntegerAndUnLock(jsvObjectGetChild(connection,HTTP_NAME_SOCKET,0))-1; // so -1 if undefined bool hadHeaders = jsvGetBoolAndUnLock(jsvObjectGetChild(connection,HTTP_NAME_HAD_HEADERS,0)); JsVar *receiveData = jsvObjectGetChild(connection,HTTP_NAME_RECEIVE_DATA,0); /* We do this up here because we want to wait until we have been once * around the idle loop (=callbacks have been executed) before we run this */ if (hadHeaders && receiveData) { JsVar *resVar = jsvObjectGetChild(connection,HTTP_NAME_RESPONSE_VAR,0); jsiQueueObjectCallbacks(resVar, HTTP_NAME_ON_DATA, receiveData, 0); jsvUnLock(resVar); // clear - because we have issued a callback jsvObjectSetChild(connection,HTTP_NAME_RECEIVE_DATA,0); } if (sckt!=INVALID_SOCKET) { JsVar *sendData = jsvObjectGetChild(connection,HTTP_NAME_SEND_DATA,0); // send data if possible if (sendData) { // this will wait to see if we can write any more, but ALSO // will wait for connection fd_set writefds; FD_ZERO(&writefds); FD_SET(sckt, &writefds); struct timeval time; time.tv_sec = 0; time.tv_usec = 0; int n = select(sckt+1, 0, &writefds, 0, &time); if (n==SOCKET_ERROR ) { // we probably disconnected so just get rid of this closeConnectionNow = true; } else if (FD_ISSET(sckt, &writefds)) { if (!_http_send(sckt, &sendData)) closeConnectionNow = true; jsvObjectSetChild(connection, HTTP_NAME_SEND_DATA, sendData); // _http_send prob updated sendData } #ifdef USE_CC3000 } else { // When in CC3000, write then read (FIXME) #else } // When in Linux, just read and write at the same time { #endif // Now receive data fd_set s; FD_ZERO(&s); FD_SET(sckt,&s); // check for waiting clients struct timeval timeout; timeout.tv_sec = 0; #ifdef USE_CC3000 timeout.tv_usec = 5000; // 5 millisec #else timeout.tv_usec = 0; #endif int n = select(sckt+1,&s,NULL,NULL,&timeout); if (n==SOCKET_ERROR) { // we probably disconnected so just get rid of this closeConnectionNow = true; } else if (n>0) { // receive data int num = (int)recv(sckt,buf,sizeof(buf),0); // add it to our request string if (num>0) { if (!receiveData) { receiveData = jsvNewFromEmptyString(); jsvObjectSetChild(connection, HTTP_NAME_RECEIVE_DATA, receiveData); } if (receiveData) { // could be out of memory jsvAppendStringBuf(receiveData, buf, num); if (!hadHeaders) { JsVar *resVar = jsvObjectGetChild(connection,HTTP_NAME_RESPONSE_VAR,0); if (httpParseHeaders(&receiveData, resVar, false)) { hadHeaders = true; jsvUnLock(jsvObjectSetChild(connection, HTTP_NAME_HAD_HEADERS, jsvNewFromBool(hadHeaders))); jsiQueueObjectCallbacks(connection, HTTP_NAME_ON_CONNECT, resVar, 0); } jsvUnLock(resVar); jsvObjectSetChild(connection, HTTP_NAME_RECEIVE_DATA, receiveData); } } } else if (num==0) { // select says data, but recv says 0 means connection is closed closeConnectionNow = true; } } else { #ifdef USE_CC3000 // Nothing to send or receive, and closed if (!sendData && cc3000_socket_has_closed(sckt)) closeConnectionNow = true; #endif } } jsvUnLock(sendData); } jsvUnLock(receiveData); if (closeConnectionNow) { JsVar *resVar = jsvObjectGetChild(connection,HTTP_NAME_RESPONSE_VAR,0); jsiQueueObjectCallbacks(resVar, HTTP_NAME_ON_CLOSE, 0, 0); jsvUnLock(resVar); _httpClientConnectionKill(connection); JsVar *connectionName = jsvArrayIteratorGetIndex(&it); jsvArrayIteratorNext(&it); jsvRemoveChild(arr, connectionName); jsvUnLock(connectionName); } else jsvArrayIteratorNext(&it); jsvUnLock(connection); } jsvUnLock(arr); }
bool socketServerConnectionsIdle(JsNetwork *net) { char buf[64]; JsVar *arr = socketGetArray(HTTP_ARRAY_HTTP_SERVER_CONNECTIONS,false); if (!arr) return false; bool hadSockets = false; JsvObjectIterator it; jsvObjectIteratorNew(&it, arr); while (jsvObjectIteratorHasValue(&it)) { hadSockets = true; // Get connection, socket, and socket type // For normal sockets, socket==connection, but for HTTP we split it into a request and a response JsVar *connection = jsvObjectIteratorGetValue(&it); SocketType socketType = socketGetType(connection); JsVar *socket = (socketType==ST_HTTP) ? jsvObjectGetChild(connection,HTTP_NAME_RESPONSE_VAR,0) : jsvLockAgain(connection); int sckt = (int)jsvGetIntegerAndUnLock(jsvObjectGetChild(connection,HTTP_NAME_SOCKET,0))-1; // so -1 if undefined bool closeConnectionNow = jsvGetBoolAndUnLock(jsvObjectGetChild(connection, HTTP_NAME_CLOSENOW, false)); if (!closeConnectionNow) { int num = net->recv(net, sckt, buf,sizeof(buf)); if (num<0) { // we probably disconnected so just get rid of this closeConnectionNow = true; } else { // add it to our request string if (num>0) { JsVar *receiveData = jsvObjectGetChild(connection,HTTP_NAME_RECEIVE_DATA,0); JsVar *oldReceiveData = receiveData; if (!receiveData) receiveData = jsvNewFromEmptyString(); if (receiveData) { jsvAppendStringBuf(receiveData, buf, (size_t)num); bool hadHeaders = jsvGetBoolAndUnLock(jsvObjectGetChild(connection,HTTP_NAME_HAD_HEADERS,0)); if (!hadHeaders && httpParseHeaders(&receiveData, connection, true)) { hadHeaders = true; jsvUnLock(jsvObjectSetChild(connection, HTTP_NAME_HAD_HEADERS, jsvNewFromBool(hadHeaders))); JsVar *server = jsvObjectGetChild(connection,HTTP_NAME_SERVER_VAR,0); JsVar *args[2] = { connection, socket }; jsiQueueObjectCallbacks(server, HTTP_NAME_ON_CONNECT, args, (socketType==ST_HTTP) ? 2 : 1); jsvUnLock(server); } if (hadHeaders && !jsvIsEmptyString(receiveData)) { // execute 'data' callback or save data jswrap_stream_pushData(connection, receiveData); // clear received data jsvUnLock(receiveData); receiveData = 0; } // if received data changed, update it if (receiveData != oldReceiveData) jsvObjectSetChild(connection,HTTP_NAME_RECEIVE_DATA,receiveData); jsvUnLock(receiveData); } } } // send data if possible JsVar *sendData = jsvObjectGetChild(socket,HTTP_NAME_SEND_DATA,0); if (sendData) { if (!socketSendData(net, socket, sckt, &sendData)) closeConnectionNow = true; jsvObjectSetChild(socket, HTTP_NAME_SEND_DATA, sendData); // socketSendData prob updated sendData } // only close if we want to close, have no data to send, and aren't receiving data if (jsvGetBoolAndUnLock(jsvObjectGetChild(socket,HTTP_NAME_CLOSE,0)) && !sendData && num<=0) closeConnectionNow = true; jsvUnLock(sendData); } if (closeConnectionNow) { // send out any data that we were POSTed JsVar *receiveData = jsvObjectGetChild(connection,HTTP_NAME_RECEIVE_DATA,0); bool hadHeaders = jsvGetBoolAndUnLock(jsvObjectGetChild(connection,HTTP_NAME_HAD_HEADERS,0)); if (hadHeaders && !jsvIsEmptyString(receiveData)) { // execute 'data' callback or save data jswrap_stream_pushData(connection, receiveData); } jsvUnLock(receiveData); // fire the close listeners jsiQueueObjectCallbacks(connection, HTTP_NAME_ON_CLOSE, 0, 0); jsiQueueObjectCallbacks(socket, HTTP_NAME_ON_CLOSE, 0, 0); _socketConnectionKill(net, connection); JsVar *connectionName = jsvObjectIteratorGetKey(&it); jsvObjectIteratorNext(&it); jsvRemoveChild(arr, connectionName); jsvUnLock(connectionName); } else jsvObjectIteratorNext(&it); jsvUnLock(connection); jsvUnLock(socket); } jsvObjectIteratorFree(&it); jsvUnLock(arr); return hadSockets; }
bool socketClientConnectionsIdle(JsNetwork *net) { char buf[64]; JsVar *arr = socketGetArray(HTTP_ARRAY_HTTP_CLIENT_CONNECTIONS,false); if (!arr) return false; bool hadSockets = false; JsvObjectIterator it; jsvObjectIteratorNew(&it, arr); while (jsvObjectIteratorHasValue(&it)) { hadSockets = true; // Get connection, socket, and socket type // For normal sockets, socket==connection, but for HTTP we split it into a request and a response JsVar *connection = jsvObjectIteratorGetValue(&it); SocketType socketType = socketGetType(connection); JsVar *socket = (socketType==ST_HTTP) ? jsvObjectGetChild(connection,HTTP_NAME_RESPONSE_VAR,0) : jsvLockAgain(connection); bool closeConnectionNow = jsvGetBoolAndUnLock(jsvObjectGetChild(connection, HTTP_NAME_CLOSENOW, false)); int sckt = (int)jsvGetIntegerAndUnLock(jsvObjectGetChild(connection,HTTP_NAME_SOCKET,0))-1; // so -1 if undefined if (sckt<0) closeConnectionNow = true; bool hadHeaders = true; if (socketType==ST_HTTP) hadHeaders = jsvGetBoolAndUnLock(jsvObjectGetChild(connection,HTTP_NAME_HAD_HEADERS,0)); JsVar *receiveData = jsvObjectGetChild(connection,HTTP_NAME_RECEIVE_DATA,0); /* We do this up here because we want to wait until we have been once * around the idle loop (=callbacks have been executed) before we run this */ if (hadHeaders) socketClientPushReceiveData(connection, socket, &receiveData); if (!closeConnectionNow) { JsVar *sendData = jsvObjectGetChild(connection,HTTP_NAME_SEND_DATA,0); // send data if possible if (sendData) { bool b = socketSendData(net, connection, sckt, &sendData); if (!b) closeConnectionNow = true; jsvObjectSetChild(connection, HTTP_NAME_SEND_DATA, sendData); // _http_send prob updated sendData } else { if (jsvGetBoolAndUnLock(jsvObjectGetChild(connection, HTTP_NAME_CLOSE, false))) closeConnectionNow = true; } // Now read data if possible int num = net->recv(net, sckt, buf, sizeof(buf)); if (num<0) { // we probably disconnected so just get rid of this closeConnectionNow = true; } else { // add it to our request string if (num>0) { if (!receiveData) { receiveData = jsvNewFromEmptyString(); jsvObjectSetChild(connection, HTTP_NAME_RECEIVE_DATA, receiveData); } if (receiveData) { // could be out of memory jsvAppendStringBuf(receiveData, buf, (size_t)num); if (socketType==ST_HTTP && !hadHeaders) { JsVar *resVar = jsvObjectGetChild(connection,HTTP_NAME_RESPONSE_VAR,0); if (httpParseHeaders(&receiveData, resVar, false)) { hadHeaders = true; jsvUnLock(jsvObjectSetChild(connection, HTTP_NAME_HAD_HEADERS, jsvNewFromBool(hadHeaders))); jsiQueueObjectCallbacks(connection, HTTP_NAME_ON_CONNECT, &resVar, 1); } jsvUnLock(resVar); jsvObjectSetChild(connection, HTTP_NAME_RECEIVE_DATA, receiveData); } } } } jsvUnLock(sendData); } if (closeConnectionNow) { socketClientPushReceiveData(connection, socket, &receiveData); if (socketType != ST_HTTP) jsiQueueObjectCallbacks(socket, HTTP_NAME_ON_END, 0, 0); jsiQueueObjectCallbacks(socket, HTTP_NAME_ON_CLOSE, 0, 0); _socketConnectionKill(net, connection); JsVar *connectionName = jsvObjectIteratorGetKey(&it); jsvObjectIteratorNext(&it); jsvRemoveChild(arr, connectionName); jsvUnLock(connectionName); } else { jsvObjectIteratorNext(&it); } jsvUnLock(receiveData); jsvUnLock(connection); jsvUnLock(socket); } jsvUnLock(arr); return hadSockets; }
static void handlePipeClose(JsVar *arr, JsvObjectIterator *it, JsVar* pipe) { jsiQueueObjectCallbacks(pipe, "#oncomplete", &pipe, 1); // Check the source to see if there was more data... It may not be a stream, // but if it is and it has data it should have a a STREAM_BUFFER_NAME field JsVar *source = jsvObjectGetChild(pipe,"source",0); JsVar *destination = jsvObjectGetChild(pipe,"destination",0); if (source && destination) { JsVar *buffer = jsvObjectGetChild(source, STREAM_BUFFER_NAME, 0); if (buffer && jsvGetStringLength(buffer)) { jsvObjectSetChild(source, STREAM_BUFFER_NAME, 0); // remove outstanding data /* call write fn - we ignore drain/etc here because the source has just closed and we want to get this sorted quickly */ JsVar *writeFunc = jspGetNamedField(destination, "write", false); if (jsvIsFunction(writeFunc)) // do the objects have the necessary methods on them? jsvUnLock(jspExecuteFunction(writeFunc, destination, 1, &buffer)); jsvUnLock(writeFunc); // update position JsVar *position = jsvObjectGetChild(pipe,"position",0); jsvSetInteger(position, jsvGetInteger(position) + (JsVarInt)jsvGetStringLength(buffer)); jsvUnLock(position); } jsvUnLock(buffer); } // also call 'end' if 'end' was passed as an initialisation option if (jsvGetBoolAndUnLock(jsvObjectGetChild(pipe,"end",0))) { // call destination.end if available if (destination) { // remove our drain and close listeners. // TODO: This removes ALL listeners. Maybe we should just remove ours? jswrap_object_removeAllListeners_cstr(destination, "drain"); jswrap_object_removeAllListeners_cstr(destination, "close"); // execute the 'end' function JsVar *endFunc = jspGetNamedField(destination, "end", false); if (endFunc) { jsvUnLock(jspExecuteFunction(endFunc, destination, 0, 0)); jsvUnLock(endFunc); } // execute the 'close' function JsVar *closeFunc = jspGetNamedField(destination, "close", false); if (closeFunc) { jsvUnLock(jspExecuteFunction(closeFunc, destination, 0, 0)); jsvUnLock(closeFunc); } } /* call source.close if available - probably not what node does but seems very sensible in this case. If you don't want it, set end:false */ if (source) { // TODO: This removes ALL listeners. Maybe we should just remove ours? jswrap_object_removeAllListeners_cstr(source, "close"); // execute the 'close' function JsVar *closeFunc = jspGetNamedField(source, "close", false); if (closeFunc) { jsvUnLock(jspExecuteFunction(closeFunc, source, 0, 0)); jsvUnLock(closeFunc); } } } jsvUnLock(source); jsvUnLock(destination); JsVar *idx = jsvObjectIteratorGetKey(it); jsvRemoveChild(arr,idx); jsvUnLock(idx); }
bool socketServerConnectionsIdle(JsNetwork *net) { char *buf = alloca(net->chunkSize); // allocate on stack JsVar *arr = socketGetArray(HTTP_ARRAY_HTTP_SERVER_CONNECTIONS,false); if (!arr) return false; bool hadSockets = false; JsvObjectIterator it; jsvObjectIteratorNew(&it, arr); while (jsvObjectIteratorHasValue(&it)) { hadSockets = true; // Get connection, socket, and socket type // For normal sockets, socket==connection, but for HTTP we split it into a request and a response JsVar *connection = jsvObjectIteratorGetValue(&it); SocketType socketType = socketGetType(connection); JsVar *socket = ((socketType&ST_TYPE_MASK)==ST_HTTP) ? jsvObjectGetChild(connection,HTTP_NAME_RESPONSE_VAR,0) : jsvLockAgain(connection); int sckt = (int)jsvGetIntegerAndUnLock(jsvObjectGetChild(connection,HTTP_NAME_SOCKET,0))-1; // so -1 if undefined bool closeConnectionNow = jsvGetBoolAndUnLock(jsvObjectGetChild(connection, HTTP_NAME_CLOSENOW, false)); int error = 0; if (!closeConnectionNow) { int num = netRecv(net, sckt, buf, net->chunkSize); if (num<0) { // we probably disconnected so just get rid of this closeConnectionNow = true; error = num; } else { // add it to our request string if (num>0) { JsVar *receiveData = jsvObjectGetChild(connection,HTTP_NAME_RECEIVE_DATA,0); JsVar *oldReceiveData = receiveData; if (!receiveData) receiveData = jsvNewFromEmptyString(); if (receiveData) { jsvAppendStringBuf(receiveData, buf, (size_t)num); bool hadHeaders = jsvGetBoolAndUnLock(jsvObjectGetChild(connection,HTTP_NAME_HAD_HEADERS,0)); if (!hadHeaders && httpParseHeaders(&receiveData, connection, true)) { hadHeaders = true; jsvObjectSetChildAndUnLock(connection, HTTP_NAME_HAD_HEADERS, jsvNewFromBool(hadHeaders)); JsVar *server = jsvObjectGetChild(connection,HTTP_NAME_SERVER_VAR,0); JsVar *args[2] = { connection, socket }; jsiQueueObjectCallbacks(server, HTTP_NAME_ON_CONNECT, args, ((socketType&ST_TYPE_MASK)==ST_HTTP) ? 2 : 1); jsvUnLock(server); } if (hadHeaders && !jsvIsEmptyString(receiveData)) { // Keep track of how much we received (so we can close once we have it) if ((socketType&ST_TYPE_MASK)==ST_HTTP) { jsvObjectSetChildAndUnLock(connection, HTTP_NAME_RECEIVE_COUNT, jsvNewFromInteger( jsvGetIntegerAndUnLock(jsvObjectGetChild(connection, HTTP_NAME_RECEIVE_COUNT, JSV_INTEGER)) + jsvGetStringLength(receiveData) )); } // execute 'data' callback or save data if (jswrap_stream_pushData(connection, receiveData, false)) { // clear received data jsvUnLock(receiveData); receiveData = 0; } } // if received data changed, update it if (receiveData != oldReceiveData) jsvObjectSetChild(connection,HTTP_NAME_RECEIVE_DATA,receiveData); jsvUnLock(receiveData); } } } // send data if possible JsVar *sendData = jsvObjectGetChild(socket,HTTP_NAME_SEND_DATA,0); if (sendData && !jsvIsEmptyString(sendData)) { int sent = socketSendData(net, socket, sckt, &sendData); // FIXME? checking for errors is a bit iffy. With the esp8266 network that returns // varied error codes we'd want to skip SOCKET_ERR_CLOSED and let the recv side deal // with normal closing so we don't miss the tail of what's received, but other drivers // return -1 (which is the same value) for all errors. So we rely on the check ~12 lines // down if(num>0)closeConnectionNow=false instead. if (sent < 0) { closeConnectionNow = true; error = sent; } jsvObjectSetChild(socket, HTTP_NAME_SEND_DATA, sendData); // socketSendData prob updated sendData } // only close if we want to close, have no data to send, and aren't receiving data bool wantClose = jsvGetBoolAndUnLock(jsvObjectGetChild(socket,HTTP_NAME_CLOSE,0)); if (wantClose && (!sendData || jsvIsEmptyString(sendData)) && num<=0) { bool reallyCloseNow = true; if ((socketType&ST_TYPE_MASK)==ST_HTTP) { // Check if we had a Content-Length header - if so, we need to wait until we have received that amount JsVar *headers = jsvObjectGetChild(connection,"headers",0); if (headers) { JsVarInt contentLength = jsvGetIntegerAndUnLock(jsvObjectGetChild(headers,"Content-Length",0)); JsVarInt contentReceived = jsvGetIntegerAndUnLock(jsvObjectGetChild(connection, HTTP_NAME_RECEIVE_COUNT, 0)); if (contentLength > contentReceived) { reallyCloseNow = false; } jsvUnLock(headers); } } closeConnectionNow = reallyCloseNow; } else if (num > 0) closeConnectionNow = false; // guarantee that anything received is processed jsvUnLock(sendData); } if (closeConnectionNow) { // send out any data that we were POSTed JsVar *receiveData = jsvObjectGetChild(connection,HTTP_NAME_RECEIVE_DATA,0); bool hadHeaders = jsvGetBoolAndUnLock(jsvObjectGetChild(connection,HTTP_NAME_HAD_HEADERS,0)); if (hadHeaders && !jsvIsEmptyString(receiveData)) { // execute 'data' callback or save data jswrap_stream_pushData(connection, receiveData, true); } jsvUnLock(receiveData); // fire error events bool hadError = fireErrorEvent(error, connection, socket); // fire the close listeners JsVar *params[1] = { jsvNewFromBool(hadError) }; jsiQueueObjectCallbacks(connection, HTTP_NAME_ON_CLOSE, params, 1); jsiQueueObjectCallbacks(socket, HTTP_NAME_ON_CLOSE, params, 1); jsvUnLock(params[0]); _socketConnectionKill(net, connection); JsVar *connectionName = jsvObjectIteratorGetKey(&it); jsvObjectIteratorNext(&it); jsvRemoveChild(arr, connectionName); jsvUnLock(connectionName); } else jsvObjectIteratorNext(&it); jsvUnLock2(connection, socket); } jsvObjectIteratorFree(&it); jsvUnLock(arr); return hadSockets; }
bool socketClientConnectionsIdle(JsNetwork *net) { char *buf = alloca(net->chunkSize); // allocate on stack JsVar *arr = socketGetArray(HTTP_ARRAY_HTTP_CLIENT_CONNECTIONS,false); if (!arr) return false; bool hadSockets = false; JsvObjectIterator it; jsvObjectIteratorNew(&it, arr); while (jsvObjectIteratorHasValue(&it)) { hadSockets = true; // Get connection, socket, and socket type // For normal sockets, socket==connection, but for HTTP connection is httpCRq and socket is httpCRs JsVar *connection = jsvObjectIteratorGetValue(&it); SocketType socketType = socketGetType(connection); JsVar *socket = ((socketType&ST_TYPE_MASK)==ST_HTTP) ? jsvObjectGetChild(connection,HTTP_NAME_RESPONSE_VAR,0) : jsvLockAgain(connection); bool socketClosed = false; JsVar *receiveData = 0; bool hadHeaders = false; int error = 0; // error code received from netXxxx functions bool isHttp = (socketType&ST_TYPE_MASK) == ST_HTTP; bool closeConnectionNow = jsvGetBoolAndUnLock(jsvObjectGetChild(connection, HTTP_NAME_CLOSENOW, false)); bool alreadyConnected = jsvGetBoolAndUnLock(jsvObjectGetChild(connection, HTTP_NAME_CONNECTED, false)); int sckt = (int)jsvGetIntegerAndUnLock(jsvObjectGetChild(connection,HTTP_NAME_SOCKET,0))-1; // so -1 if undefined if (sckt>=0) { if (isHttp) hadHeaders = jsvGetBoolAndUnLock(jsvObjectGetChild(connection,HTTP_NAME_HAD_HEADERS,0)); else hadHeaders = true; receiveData = jsvObjectGetChild(connection,HTTP_NAME_RECEIVE_DATA,0); /* We do this up here because we want to wait until we have been once * around the idle loop (=callbacks have been executed) before we run this */ if (hadHeaders) socketClientPushReceiveData(connection, socket, &receiveData); JsVar *sendData = jsvObjectGetChild(connection,HTTP_NAME_SEND_DATA,0); if (!closeConnectionNow) { // send data if possible if (sendData && !jsvIsEmptyString(sendData)) { // don't try to send if we're already in error state int num = 0; if (error == 0) num = socketSendData(net, connection, sckt, &sendData); if (num > 0 && !alreadyConnected && !isHttp) { // whoa, we sent something, must be connected! jsiQueueObjectCallbacks(connection, HTTP_NAME_ON_CONNECT, &connection, 1); jsvObjectSetChildAndUnLock(connection, HTTP_NAME_CONNECTED, jsvNewFromBool(true)); alreadyConnected = true; } if (num < 0) { closeConnectionNow = true; error = num; } jsvObjectSetChild(connection, HTTP_NAME_SEND_DATA, sendData); // _http_send prob updated sendData } else { // no data to send, do we want to close? do so. if (jsvGetBoolAndUnLock(jsvObjectGetChild(connection, HTTP_NAME_CLOSE, false))) closeConnectionNow = true; } // Now read data if possible (and we have space for it) if (!receiveData || !hadHeaders) { int num = netRecv(net, sckt, buf, net->chunkSize); //if (num != 0) printf("recv returned %d\r\n", num); if (!alreadyConnected && num == SOCKET_ERR_NO_CONN) { ; // ignore... it's just telling us we're not connected yet } else if (num < 0) { closeConnectionNow = true; error = num; // disconnected without headers? error. if (!hadHeaders && error == SOCKET_ERR_CLOSED) error = SOCKET_ERR_NO_RESP; } else { // did we just get connected? if (!alreadyConnected && !isHttp) { jsiQueueObjectCallbacks(connection, HTTP_NAME_ON_CONNECT, &connection, 1); jsvObjectSetChildAndUnLock(connection, HTTP_NAME_CONNECTED, jsvNewFromBool(true)); alreadyConnected = true; // if we do not have any data to send, issue a drain event if (!sendData || (int)jsvGetStringLength(sendData) == 0) jsiQueueObjectCallbacks(connection, HTTP_NAME_ON_DRAIN, &connection, 1); } // got data add it to our receive buffer if (num > 0) { if (!receiveData) { receiveData = jsvNewFromEmptyString(); jsvObjectSetChild(connection, HTTP_NAME_RECEIVE_DATA, receiveData); } if (receiveData) { // could be out of memory jsvAppendStringBuf(receiveData, buf, (size_t)num); if ((socketType&ST_TYPE_MASK)==ST_HTTP && !hadHeaders) { // for HTTP see whether we now have full response headers JsVar *resVar = jsvObjectGetChild(connection,HTTP_NAME_RESPONSE_VAR,0); if (httpParseHeaders(&receiveData, resVar, false)) { hadHeaders = true; jsvObjectSetChildAndUnLock(connection, HTTP_NAME_HAD_HEADERS, jsvNewFromBool(hadHeaders)); jsiQueueObjectCallbacks(connection, HTTP_NAME_ON_CONNECT, &resVar, 1); } jsvUnLock(resVar); jsvObjectSetChild(connection, HTTP_NAME_RECEIVE_DATA, receiveData); } } } } } jsvUnLock(sendData); } } if (closeConnectionNow) { socketClientPushReceiveData(connection, socket, &receiveData); if (!receiveData) { if ((socketType&ST_TYPE_MASK) != ST_HTTP) jsiQueueObjectCallbacks(socket, HTTP_NAME_ON_END, &socket, 1); // If we had data to send but the socket closed, this is an error JsVar *sendData = jsvObjectGetChild(connection,HTTP_NAME_SEND_DATA,0); if (sendData && jsvGetStringLength(sendData) > 0 && error == SOCKET_ERR_CLOSED) error = SOCKET_ERR_UNSENT_DATA; jsvUnLock(sendData); _socketConnectionKill(net, connection); JsVar *connectionName = jsvObjectIteratorGetKey(&it); jsvObjectIteratorNext(&it); jsvRemoveChild(arr, connectionName); jsvUnLock(connectionName); socketClosed = true; // fire error event, if there is an error bool hadError = fireErrorEvent(error, connection, NULL); // close callback must happen after error callback JsVar *params[1] = { jsvNewFromBool(hadError) }; jsiQueueObjectCallbacks(socket, HTTP_NAME_ON_CLOSE, params, 1); jsvUnLock(params[0]); } } if (!socketClosed) { jsvObjectIteratorNext(&it); } jsvUnLock3(receiveData, connection, socket); } jsvUnLock(arr); return hadSockets; }
void clientRequestWrite(JsNetwork *net, JsVar *httpClientReqVar, JsVar *data) { SocketType socketType = socketGetType(httpClientReqVar); // Append data to sendData JsVar *sendData = jsvObjectGetChild(httpClientReqVar, HTTP_NAME_SEND_DATA, 0); if (!sendData) { JsVar *options = 0; // Only append a header if we're doing HTTP AND we haven't already connected if ((socketType&ST_TYPE_MASK) == ST_HTTP) if (jsvGetIntegerAndUnLock(jsvObjectGetChild(httpClientReqVar, HTTP_NAME_SOCKET, 0))==0) options = jsvObjectGetChild(httpClientReqVar, HTTP_NAME_OPTIONS_VAR, 0); if (options) { // We're an HTTP client - make a header JsVar *method = jsvObjectGetChild(options, "method", 0); JsVar *path = jsvObjectGetChild(options, "path", 0); sendData = jsvVarPrintf("%v %v HTTP/1.0\r\nUser-Agent: Espruino "JS_VERSION"\r\nConnection: close\r\n", method, path); jsvUnLock2(method, path); JsVar *headers = jsvObjectGetChild(options, "headers", 0); bool hasHostHeader = false; if (jsvIsObject(headers)) { JsVar *hostHeader = jsvObjectGetChild(headers, "Host", 0); hasHostHeader = hostHeader!=0; jsvUnLock(hostHeader); httpAppendHeaders(sendData, headers); // if Transfer-Encoding:chunked was set, subsequent writes need to 'chunk' the data that is sent if (jsvIsStringEqualAndUnLock(jsvObjectGetChild(headers, "Transfer-Encoding", 0), "chunked")) { jsvObjectSetChildAndUnLock(httpClientReqVar, HTTP_NAME_CHUNKED, jsvNewFromBool(true)); } } jsvUnLock(headers); if (!hasHostHeader) { JsVar *host = jsvObjectGetChild(options, "host", 0); int port = (int)jsvGetIntegerAndUnLock(jsvObjectGetChild(options, "port", 0)); if (port>0 && port!=80) jsvAppendPrintf(sendData, "Host: %v:%d\r\n", host, port); else jsvAppendPrintf(sendData, "Host: %v\r\n", host); jsvUnLock(host); } // finally add ending newline jsvAppendString(sendData, "\r\n"); } else { // !options // We're not HTTP (or were already connected), so don't send any header sendData = jsvNewFromString(""); } jsvObjectSetChild(httpClientReqVar, HTTP_NAME_SEND_DATA, sendData); jsvUnLock(options); } // We have data and aren't out of memory... if (data && sendData) { // append the data to what we want to send JsVar *s = jsvAsString(data, false); if (s) { if ((socketType&ST_TYPE_MASK) == ST_HTTP && jsvGetBoolAndUnLock(jsvObjectGetChild(httpClientReqVar, HTTP_NAME_CHUNKED, 0))) { // If we asked to send 'chunked' data, we need to wrap it up, // prefixed with the length jsvAppendPrintf(sendData, "%x\r\n%v\r\n", jsvGetStringLength(s), s); } else { jsvAppendStringVarComplete(sendData,s); } jsvUnLock(s); } } jsvUnLock(sendData); if ((socketType&ST_TYPE_MASK) == ST_HTTP) { // on HTTP we connect after the first write clientRequestConnect(net, httpClientReqVar); } }
/// if host=0, creates a server otherwise creates a client (and automatically connects). Returns >=0 on success int net_linux_createsocket(JsNetwork *net, SocketType socketType, uint32_t host, unsigned short port, JsVar *options) { NOT_USED(net); int ippProto = socketType & ST_UDP ? IPPROTO_UDP : IPPROTO_TCP; int scktType = socketType & ST_UDP ? SOCK_DGRAM : SOCK_STREAM; int sckt = -1; if (host!=0) { // ------------------------------------------------- host (=client) sckt = socket(AF_INET, scktType, ippProto); if (sckt<0) { jsError("Socket creation failed"); return sckt; // error } // turn on non-blocking mode #ifdef WIN_OS u_long n = 1; ioctlsocket(sckt,FIONBIO,&n); #endif if (scktType == SOCK_DGRAM) { // only for UDP // set broadcast int optval = 1; if (setsockopt(sckt,SOL_SOCKET,SO_BROADCAST,(const char *)&optval,sizeof(optval))<0) jsWarn("setsockopt(SO_BROADCAST) failed\n"); return sckt; } sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_addr.s_addr = (in_addr_t)host; sin.sin_port = htons( port ); int res = connect(sckt,(struct sockaddr *)&sin, sizeof(sockaddr_in) ); if (res == SOCKET_ERROR) { #ifdef WIN_OS int err = WSAGetLastError(); #else int err = errno; #endif if (err != EINPROGRESS && err != EWOULDBLOCK) { jsError("Connect failed (err %d)", err); closesocket(sckt); return -1; } } } else { // ------------------------------------------------- no host (=server) sckt = socket(AF_INET, scktType, ippProto); if (sckt == INVALID_SOCKET) { jsError("Socket creation failed"); return 0; } if (jsvGetBoolAndUnLock(jsvObjectGetChild(options, "reuseAddr", 0))) { int optval = 1; if (setsockopt(sckt,SOL_SOCKET,SO_REUSEADDR,(const char *)&optval,sizeof(optval)) < 0) jsWarn("setsockopt(SO_REUSADDR) failed\n"); #ifdef SO_REUSEPORT if (setsockopt(sckt,SOL_SOCKET,SO_REUSEPORT,(const char *)&optval,sizeof(optval)) < 0) jsWarn("setsockopt(SO_REUSPORT) failed\n"); #endif } int nret; sockaddr_in serverInfo; memset(&serverInfo, 0, sizeof(serverInfo)); serverInfo.sin_family = AF_INET; //serverInfo.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // allow only LOCAL clients to connect serverInfo.sin_addr.s_addr = INADDR_ANY; // allow anyone to connect serverInfo.sin_port = (unsigned short)htons((unsigned short)port); // port nret = bind(sckt, (struct sockaddr*)&serverInfo, sizeof(serverInfo)); if (nret == SOCKET_ERROR) { jsError("Socket bind failed"); closesocket(sckt); return -1; } // multicast support // FIXME: perhaps extend the JsNetwork with addmembership/removemembership instead of using options JsVar *mgrpVar = jsvObjectGetChild(options, "multicastGroup", 0); if (mgrpVar) { char ipStr[18]; uint32_t grpip; jsvGetString(mgrpVar, ipStr, sizeof(ipStr)); jsvUnLock(mgrpVar); net_linux_gethostbyname(net, ipStr, &grpip); JsVar *ipVar = jsvObjectGetChild(options, "multicastIp", 0); jsvGetString(ipVar, ipStr, sizeof(ipStr)); jsvUnLock(ipVar); uint32_t ip; net_linux_gethostbyname(net, ipStr, &ip); struct ip_mreq mreq; mreq.imr_multiaddr = *(struct in_addr *)&grpip; mreq.imr_interface = *(struct in_addr *)&ip; setsockopt (sckt, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); } if (scktType == SOCK_STREAM) { // only for TCP // Make the socket listen nret = listen(sckt, 10); // 10 connections (but this ignored on CC30000) if (nret == SOCKET_ERROR) { jsError("Socket listen failed"); closesocket(sckt); return -1; } } } #ifdef SO_NOSIGPIPE // disable SIGPIPE int optval = 1; if (setsockopt(sckt,SOL_SOCKET,SO_NOSIGPIPE,(const char *)&optval,sizeof(optval))<0) jsWarn("setsockopt(SO_NOSIGPIPE) failed\n"); #endif return sckt; }
void httpServerConnectionsIdle() { char buf[64]; JsVar *arr = httpGetArray(HTTP_ARRAY_HTTP_SERVER_CONNECTIONS,false); if (!arr) return; JsArrayIterator it; jsvArrayIteratorNew(&it, arr); while (jsvArrayIteratorHasElement(&it)) { JsVar *connection = jsvArrayIteratorGetElement(&it); JsVar *connectReponse = jsvObjectGetChild(connection,HTTP_NAME_RESPONSE_VAR,0); SOCKET sckt = (SOCKET)jsvGetIntegerAndUnLock(jsvObjectGetChild(connection,HTTP_NAME_SOCKET,0))-1; // so -1 if undefined bool closeConnectionNow = jsvGetBoolAndUnLock(jsvObjectGetChild(connection, HTTP_NAME_CLOSENOW, false)); bool hadData = false; // TODO: look for unreffed connections? fd_set s; FD_ZERO(&s); FD_SET(sckt,&s); // check for waiting clients struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0; int n = select(sckt+1,&s,NULL,NULL,&timeout); if (n==SOCKET_ERROR) { // we probably disconnected so just get rid of this closeConnectionNow = true; } else if (n>0) { hadData = true; // receive data int num = (int)recv(sckt,buf,sizeof(buf),0); // add it to our request string if (num>0) { JsVar *receiveData = jsvObjectGetChild(connection,HTTP_NAME_RECEIVE_DATA,0); if (!receiveData) { receiveData = jsvNewFromEmptyString(); jsvObjectSetChild(connection,HTTP_NAME_RECEIVE_DATA,receiveData); } if (receiveData) { jsvAppendStringBuf(receiveData, buf, num); bool hadHeaders = jsvGetBoolAndUnLock(jsvObjectGetChild(connection,HTTP_NAME_HAD_HEADERS,0)); if (!hadHeaders && httpParseHeaders(&receiveData, connection, true)) { hadHeaders = true; jsvUnLock(jsvObjectSetChild(connection, HTTP_NAME_HAD_HEADERS, jsvNewFromBool(hadHeaders))); JsVar *resVar = jsvObjectGetChild(connection,HTTP_NAME_RESPONSE_VAR,0); JsVar *server = jsvObjectGetChild(connection,HTTP_NAME_SERVER_VAR,0); jsiQueueObjectCallbacks(server, HTTP_NAME_ON_CONNECT, connection, resVar); jsvUnLock(server); jsvUnLock(resVar); } jsvUnLock(receiveData); } } } // send data if possible JsVar *sendData = jsvObjectGetChild(connectReponse,HTTP_NAME_SEND_DATA,0); if (sendData) { fd_set writefds; FD_ZERO(&writefds); FD_SET(sckt, &writefds); struct timeval time; time.tv_sec = 0; time.tv_usec = 0; int n = select(sckt+1, 0, &writefds, 0, &time); if (n==SOCKET_ERROR ) { // we probably disconnected so just get rid of this closeConnectionNow = true; } else if (FD_ISSET(sckt, &writefds)) { if (!_http_send(sckt, &sendData)) closeConnectionNow = true; } jsvObjectSetChild(connectReponse, HTTP_NAME_SEND_DATA, sendData); // _http_send prob updated sendData } else { #ifdef USE_CC3000 // nothing to send, nothing to receive, and closed... if (!hadData && cc3000_socket_has_closed(sckt)) closeConnectionNow = true; #endif } if (jsvGetBoolAndUnLock(jsvObjectGetChild(connectReponse,HTTP_NAME_CLOSE,0)) && !sendData) closeConnectionNow = true; jsvUnLock(sendData); if (closeConnectionNow) { JsVar *resVar = jsvObjectGetChild(connection,HTTP_NAME_RESPONSE_VAR,0); jsiQueueObjectCallbacks(resVar, HTTP_NAME_ON_CLOSE, 0, 0); jsvUnLock(resVar); _httpServerConnectionKill(connection); JsVar *connectionName = jsvArrayIteratorGetIndex(&it); jsvArrayIteratorNext(&it); jsvRemoveChild(arr, connectionName); jsvUnLock(connectionName); } else jsvArrayIteratorNext(&it); jsvUnLock(connection); jsvUnLock(connectReponse); } jsvArrayIteratorFree(&it); jsvUnLock(arr); }