$callback, "ttype" => $ttype, "msgtype" => $msgtype, "dtype" => $dtype]; $this->__Callbacks[] = $cb; } function execute($can, $msg, $context) { $unhandled = true; foreach ($this->__Callbacks as $cb) { if ($msg['msgtype'] == $cb['msgtype'] && $msg['ttype'] == $cb['ttype'] && $msg['dtype'] == $cb['dtype']) { call_user_func($cb['callback'], $can, $msg, $context); $unhandled = false; } } return $unhandled; } } class CAN { private $fd = null; private $deviceUid = ""; private $msgRecvRegistry = null; function __construct($iface = "can0", $deviceUid = "99123456789012345678901234567890") { if ($iface == "") return; $this->deviceUid = $deviceUid; $this->fd = di_can_open($iface); di_can_set_nodeid($this->fd, $deviceUid); $this->msgRecvRegistry = new CANMsgRegistry(); } function __destruct() { if ($this->fd != null) { di_can_close($this->fd); } } function isOpen() { if ($this->fd) { return true; } return false; } public function listInterfaces() { if ($this->fd) { return di_can_list($this->fd); } else { return array(); } } /* Get DiNet timestamp of Now */ public function timeNow() { return intval(microtime(true) * 1000); } public function getDeviceUid() { return $this->deviceUid; } public function registerMsgRecvCallback($callback, $ttype, $msgtype, $dtype) { $this->msgRecvRegistry->register($callback, $ttype, $msgtype, $dtype); } public function recv($context = null) { $msg = di_can_recv($this->fd); if ($msg !== NULL) { $this->msgRecvRegistry->execute($this, $msg, $context); /* For RPC messages recode mpack to PHP */ if ($msg['msgtype'] == DI_CAN_MSGTYPE_RPC && $msg['ptype'] == DI_CAN_PTYPE_MSGPACK) { $msg['result'] = msgpack_unpack($msg['msg']); } else if ($msg['msgtype'] == DI_CAN_MSGTYPE_RAW) { // NOTE(jjacobs): we dont convert at all ... for now } } return $msg; } public function recvWait($action, $classMethod, $timeoutMs = -1) { $start = microtime(true) * 1000; while (true) { if ((microtime(true) * 1000) > $start + $timeoutMs && $timeoutMs > 0) { echo "Test failure timeout occurred"; return null; } $msg = $this->recv(); if (!$msg) continue; if ($msg['msgtype'] != DI_CAN_MSGTYPE_RPC) continue; $ttype = $this->convTtype($msg['ttype']); $dtype = $this->convDtype($msg['dtype']); if ($ttype == $action && $dtype == $classMethod) return $msg; } } /** * Conversion functions for transfertype * If dtype is a string e.g "req" -> DI_CAN_TRANSFERTYPE_REQUEST * If dtype is a int e.g DI_CAN_TRANSFERTYPE_PUBLISH -> "pub" */ private function convTtype($ttype) { $mapping = array ( "rep" => DI_CAN_TRANSFERTYPE_REPLY, "req" => DI_CAN_TRANSFERTYPE_REQUEST, "pub" => DI_CAN_TRANSFERTYPE_PUBLISH ); if (is_string($ttype)) { return $mapping[$ttype]; } else { foreach($mapping as $k => $v) { if ($v == $ttype) return $k; } return "unknown"; } } /** * Conversion functions for data type * If dtype is a string e.g "device:ping" -> DI_RPC_TYPE_DEVICE_PING * If dtype is a int e.g DI_RPC_TYPE_DEVICE_PING -> "device:ping" */ private function convDtype($dtype) { $mapping = array ( "notify:info" => DI_RPC_TYPE_NOTIFY_INFO, "notify:data" => DI_RPC_TYPE_NOTIFY_DATA, "action:info" => DI_RPC_TYPE_ACTION_INFO, "action:get" => DI_RPC_TYPE_ACTION_GET, "action:set" => DI_RPC_TYPE_ACTION_SET, "sensor:info" => DI_RPC_TYPE_SENSOR_INFO, "sensor:data" => DI_RPC_TYPE_SENSOR_DATA, "config:info" => DI_RPC_TYPE_CONFIG_INFO, "config:get" => DI_RPC_TYPE_CONFIG_GET, "config:set" => DI_RPC_TYPE_CONFIG_SET, "config:reset" => DI_RPC_TYPE_CONFIG_RESET, "device:ping" => DI_RPC_TYPE_DEVICE_PING, "device:info" => DI_RPC_TYPE_DEVICE_INFO, "device:data" => DI_RPC_TYPE_DEVICE_DATA, "device:reset" => DI_RPC_TYPE_DEVICE_RESET, "device:error" => DI_RPC_TYPE_DEVICE_ERROR ); if (is_string($dtype)) { return $mapping[$dtype]; } else { foreach($mapping as $k => $v) { if ($v == $dtype) return $k; } return "unknown"; } } public function flush() { while (true) { $msg = $this->recv(); if ($msg == NULL) break; } } public function sendRaw($msg) { $msg['msgtype'] = DI_CAN_MSGTYPE_RAW; // TODO $msg['ptype'] = DI_CAN_PTYPE_??? $msg['ttype'] = $this->convTtype($msg['ttype']); $msg['dtype'] = $this->convDtype($msg['datatype']); di_can_send($this->fd, $msg); } public function sendRPC($msg) { trigger_error('Deprecated: this function is deprecated -> use send()!', E_NOTICE); $msg['msgtype'] = DI_CAN_MSGTYPE_RPC; $msg['ptype'] = DI_CAN_PTYPE_MSGPACK; $msg['ttype'] = $this->convTtype($msg['ttype']); di_can_send($this->fd, $msg); } public function sendRawTime() { $msg = []; $msg['msgtype'] = DI_CAN_MSGTYPE_RAW; $msg['ttype'] = DI_CAN_TRANSFERTYPE_PUBLISH; $msg['ptype'] = DI_CAN_PTYPE_U64; $msg['dtype'] = DI_CAN_RAW_DTYPE_DINET_TIME; $msg['msg'] = intval(round(microtime(true) * 1000)); di_can_send($this->fd, $msg); } public function send($msg) { di_can_send($this->fd, $msg); } public function msgQueue($msg, $interval_ms) { return di_can_send_queue($this->fd, $msg, $interval_ms); } public function msgDequeue($token) { di_can_send_dequeue($this->fd, $token); } /** * Set device state (token, activation) * state: "idle", "armed", "active" * wait: for WUM tests, we need to wait in between IDLE->ARMED * and ARMED- ->IDLE state changes for the single sound to * finish. Default is '0' (don't wait) for DUU and DUM testing */ public function device_set_state(&$test, $state, $wait = 0) { $s = $this->rpc_request_device_state(); if ($s == $state) { $test->info("Device state already \"$state\", nothing to be done"); return; } $token = rand(1, 9999); // Idle -> Active if ($s == "idle" && $state == "active") { $this->rpc_request_reply($test, 'config:set', ['uid' => DI_RPC_UID_CONFIG_DEVICE_TOKEN, "value" => $token]); // for WUM we need to wait until the single sound has finished if ($wait) sleep(5); $this->rpc_request_reply($test, 'config:set',['uid' => DI_RPC_UID_CONFIG_DEVICE_ACTIVATION, "value" => true] ); } // Idle -> Armed if ($s == "idle" && $state == "armed") { $this->rpc_request_reply($test, 'config:set', ['uid' => DI_RPC_UID_CONFIG_DEVICE_TOKEN, "value" => $token]); if ($wait) sleep(5); } // Armed -> Idle if ($s == "armed" && $state == "idle") { $this->rpc_request_reply($test, 'config:reset', ['uid' => DI_RPC_UID_CONFIG_DEVICE_TOKEN]); if ($wait) sleep(5); } // Armed -> Active if ($s == "armed" && $state == "active") $this->rpc_request_reply($test, 'config:set', ['uid' => DI_RPC_UID_CONFIG_DEVICE_ACTIVATION, "value" => true]); // Active -> Armed if ($s == "active" && $state == "armed") $this->rpc_request_reply($test, 'config:reset', ['uid' => DI_RPC_UID_CONFIG_DEVICE_ACTIVATION]); // Active -> Idle if ($s == "active" && $state == "idle") { $this->rpc_request_reply($test, 'config:reset', ['uid' => DI_RPC_UID_CONFIG_DEVICE_ACTIVATION]); $this->rpc_request_reply($test, 'config:reset', ['uid' => DI_RPC_UID_CONFIG_DEVICE_TOKEN]); if ($wait) sleep(5); } $msg = $this->rpc_request_reply($test, 'device:data'); if (!$msg) { $test->failed("Expect device:data reply"); return; } $nstate = $msg['result'][0]['state']; // TODO(jjacobs): I'm not sure why result state is in an array if ($nstate != $state) $test->failed("Expected device state \"'$state'\", got \"'$nstate'\""); $test->info("Old device state: $s"); $test->info("New device state: $nstate"); } public function rpc_request_reply(&$test, $request, $params = null, $errorCode = null) { $this->flush(); $dtype = $this->convDtype($request); $msg = [ 'msgtype' => DI_CAN_MSGTYPE_RPC, 'ttype' => DI_CAN_TRANSFERTYPE_REQUEST, 'dtype' => $dtype ]; if ($params !== null) { $msg['ptype'] = DI_CAN_PTYPE_MSGPACK; $msg['msg'] = msgpack_pack($params); } else { $msg['msg'] = null; } di_can_send($this->fd, $msg); DiTestMsg::dbg("Waiting for reply: $request"); while(true) { $msg = $this->recv(); if ($msg == NULL) continue; if ($msg['msgtype'] == DI_CAN_MSGTYPE_RPC && $msg['ttype'] == DI_CAN_TRANSFERTYPE_REPLY && $msg['dtype'] == $dtype) { DiTestMsg::dbg("Got RPC reply: $request"); if ($msg['ptype'] == DI_CAN_PTYPE_MSGPACK) $msg['result'] = msgpack_unpack($msg['msg']); return $msg; } /** @TODO .... */ /* if ($errorCode === null) { if (check_rpc_no_error($test, $request, $msg)) { break; } else { if (check_rpc_error($test, $request, $msg, $errorCode)) { break; } } } */ } } public function rpc_info_is_present(&$test, $result, $uid, $label, $type, $default = null) { if (!is_array($result)) { $test->failed("Result is not an array"); return; } $info = null; foreach ($result as $x) { if ($x['uid'] == $uid) { $info = $x; break; } } if (!$info) { $test->failed("Info not present in result (uid: $uid, label: $label, type: $type)"); return; } $test->equal($uid, $info['uid']); $test->equal($label, $info['label']); $test->equal($type, $info['type']); $default_str = ""; if ($default) { $test->equal($default, $info['default']); $default_str = ", default: $default"; } $test->info("Info result present: uid: $uid, label: $label, type: $type$default_str"); } public function rpc_request_device_state() { $this->flush(); $msg = []; $msg['msgtype'] = DI_CAN_MSGTYPE_RPC; $msg['ttype'] = DI_CAN_TRANSFERTYPE_REQUEST; $msg['dtype'] = DI_RPC_TYPE_DEVICE_DATA; di_can_send($this->fd, $msg); while(true) { $msg = $this->recv(); if ($msg === NULL) continue; if ($msg['msgtype'] == DI_CAN_MSGTYPE_RPC && $msg['ptype'] == DI_CAN_PTYPE_MSGPACK && $msg['dtype'] == DI_RPC_TYPE_DEVICE_DATA) { $result = msgpack_unpack($msg['msg']); DiTestMsg::dbg("Device state: '".$result[0]['state']."'"); return $result[0]['state']; } } } public function rpc_request_device_info() { $this->flush(); $msg = []; $msg['msgtype'] = DI_CAN_MSGTYPE_RPC; $msg['ttype'] = DI_CAN_TRANSFERTYPE_REQUEST; $msg['dtype'] = DI_RPC_TYPE_DEVICE_INFO; $msg['dst_id'] = DI_CAN_NODEID_BROADCAST; di_can_send($this->fd, $msg); while(true) { $msg = $this->recv(); if ($msg === NULL) continue; if ($msg['msgtype'] == DI_CAN_MSGTYPE_RPC && $msg['ttype'] = DI_CAN_TRANSFERTYPE_REPLY && $msg['ptype'] == DI_CAN_PTYPE_MSGPACK && $msg['dtype'] == DI_RPC_TYPE_DEVICE_INFO) { $result = msgpack_unpack($msg['msg'])[0]; DiTestMsg::dbg("Type: '".$result['type']."'"); DiTestMsg::dbg("Revision: '".$result['revision']."'"); DiTestMsg::dbg("Version: '".$result['version']."'"); return $msg; } } } public function rpc_check_battery_voltage($test, $uid, $v_min, $v_max) { while (true) { $msg = $this->recv($this->fd); if ($msg === NULL) continue; if ($msg['msgtype'] != DI_CAN_MSGTYPE_RPC) continue; if ($msg['ttype'] != DI_CAN_TRANSFERTYPE_PUBLISH) continue; if ($msg['dtype'] != DI_RPC_TYPE_SENSOR_DATA) continue; foreach ($msg['result'] as $v) { if ($v['uid'] != $uid) continue; $bat_voltage = $v['value']; if ($test->range($bat_voltage, $v_min, $v_max) == true) { $test->passed("Battery voltage OK (uid: $uid) voltage ($bat_voltage)"); return true; } } } return false; } public function rpc_check_battery_state($test, $uid, $state) { while (true) { $msg = $this->recv($this->fd); if ($msg === NULL) continue; if ($msg['msgtype'] != DI_CAN_MSGTYPE_RPC) continue; if ($msg['ttype'] != DI_CAN_TRANSFERTYPE_PUBLISH) continue; if ($msg['dtype'] != DI_RPC_TYPE_SENSOR_DATA) continue; foreach ($msg['result'] as $v) { if ($v['uid'] != $uid) continue; $bat_state= $v['value']; if ($test->equal($state, $bat_state) == true) { $test->passed("Battery state OK (uid: $uid) state ($bat_state)"); return true; } else { break; } } } return false; } public function rpc_check_charger_state($test, $uid, $state) { while (true) { $msg = $this->recv($this->fd); if ($msg === NULL) continue; if ($msg['msgtype'] != DI_CAN_MSGTYPE_RPC) continue; if ($msg['ttype'] != DI_CAN_TRANSFERTYPE_PUBLISH) continue; if ($msg['dtype'] != DI_RPC_TYPE_SENSOR_DATA) continue; foreach ($msg['result'] as $v) { if ($v['uid'] != $uid) continue; $charger_state= $v['value']; if ($state == $charger_state){ // if ($test->equal($state, $charger_state) == true) { $test->passed("Charger state OK (uid: $uid) state ($charger_state)"); return true; } else { break; } } } return false; } public function rpc_check_alarm_notify($test, $alarm_system_expect, $alarm_button_expect) { $system_alarm_handled = false; $system_alarm_result = false; $manual_alarm_handled = false; $manual_alarm_result = false; while (true) { $msg = $this->recv($this->fd); if ($msg === NULL){ continue; } if ($msg['msgtype'] != DI_CAN_MSGTYPE_RPC) continue; if ($msg['ttype'] != DI_CAN_TRANSFERTYPE_PUBLISH) continue; if ($msg['dtype'] != DI_RPC_TYPE_NOTIFY_DATA) continue; foreach ($msg['result'] as $v) { $state = $v['value']; if ($v['uid'] == DI_RPC_UID_WUM_ACTION_ALARM) { if ($test->equal($alarm_system_expect, $state) == true) { if ($state) $test->passed("Got system alarm"); else $test->passed("No system alarm"); $system_alarm_result = true; } else { $test->failed("failed on system alarm"); $system_alarm_result = false; } $system_alarm_handled = true; break; } else if ($v['uid'] == DI_RPC_UID_WUM_NOTIFY_ALARM_MANUAL) { if ($test->equal($alarm_button_expect, $state) == true) { if ($state) $test->passed("Got manual alarm"); else $test->passed("No manual alarm"); $manual_alarm_result = true; } else { $test->failed("failed on manual alarm"); $manual_alarm_result = false; } $manual_alarm_handled = true; break; } } if ($system_alarm_handled && $manual_alarm_handled) break; } return($system_alarm_result && $manual_alarm_result); } public function raw_request_device_uid() { $this->flush(); $msg = []; $msg['msgtype'] = DI_CAN_MSGTYPE_RAW; $msg['ttype'] = DI_CAN_TRANSFERTYPE_REQUEST; $msg['dtype'] = DI_CAN_RAW_DTYPE_DEVICE_UID; di_can_send($this->fd, $msg); while(true) { $msg = $this->recv(); if ($msg === NULL) continue; if ($msg['msgtype'] == DI_CAN_MSGTYPE_RAW && $msg['ttype'] == DI_CAN_TRANSFERTYPE_REPLY && $msg['ptype'] == DI_CAN_PTYPE_STRING && $msg['dtype'] == DI_CAN_RAW_DTYPE_DEVICE_UID) { DiTestMsg::info("Device uid: '".$msg['msg']."'"); return $msg['msg']; } } } public function raw_request_cloudlight_state() { $this->flush(); $msg = []; $msg['msgtype'] = DI_CAN_MSGTYPE_RAW; $msg['ttype'] = DI_CAN_TRANSFERTYPE_REQUEST; $msg['dtype'] = DI_CAN_RAW_DTYPE_CLOUDLIGHT_STATE; di_can_send($this->fd, $msg); while(true) { $msg = $this->recv(); if (!$msg) continue; if ($msg['msgtype'] == DI_CAN_MSGTYPE_RAW && $msg['ttype'] == DI_CAN_TRANSFERTYPE_REPLY && $msg['ptype'] == DI_CAN_PTYPE_U8 && $msg['dtype'] == DI_CAN_RAW_DTYPE_CLOUDLIGHT_STATE) { DiTestMsg::info("Cloudlight state: '". $msg['msg'] ."'"); return $msg['msg']; } } } public function check_rpc_no_error($test, $request, $msg) { if (isset($msg) && $msg['ttype'] == DI_CAN_TRANSFERTYPE_REPLY && $msg['dtype'] == $this->convDtype($request)) { if (!isset($msg['result']['code'])) $test->passed("Got reply without error for '$request'"); else $test->failed("Got error on '$request', expected no error"); $test->isFalse(isset($msg['result']['code'])); return true; } return false; } }