├── pihat ├── Makefile ├── python ├── jsonrpclib │ ├── __init__.py │ ├── history.py │ ├── config.py │ ├── jsonclass.py │ ├── SimpleJSONRPCServer.py │ └── jsonrpc.py ├── pi-switch.py └── pi-switch-client.py ├── status.h ├── nexa.h ├── README.md ├── status.c ├── radio.h ├── nexa.c ├── radio.c └── main.c /pihat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s7mx1/pihat/HEAD/pihat -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: main.c nexa.c status.c radio.c 2 | $(CROSS_COMPILE)gcc $(CFLAGS) -s -lm -std=c99 -o pihat main.c nexa.c status.c radio.c 3 | -------------------------------------------------------------------------------- /python/jsonrpclib/__init__.py: -------------------------------------------------------------------------------- 1 | from jsonrpclib.config import Config 2 | config = Config.instance() 3 | from jsonrpclib.history import History 4 | history = History.instance() 5 | from jsonrpclib.jsonrpc import Server, MultiCall, Fault 6 | from jsonrpclib.jsonrpc import ProtocolError, loads, dumps 7 | -------------------------------------------------------------------------------- /status.h: -------------------------------------------------------------------------------- 1 | #ifndef _STATUS_H_ 2 | #define _STATUS_H_ 3 | 4 | #include 5 | 6 | /* Time constants - in microseconds */ 7 | #define STATUS_SHORT 300 8 | #define STATUS_LONG 900 9 | #define STATUS_BURSTDELAY 10000 10 | 11 | void statusTxSymbol(uint8_t symbol); 12 | void statusTxPacket(uint64_t *d, uint8_t rep); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /nexa.h: -------------------------------------------------------------------------------- 1 | #ifndef _NEXA_H_ 2 | #define _NEXA_H_ 3 | 4 | #include 5 | 6 | /* Time constants - in microseconds */ 7 | #define NEXA_HIGH 204 8 | #define NEXA_LOWA 272 9 | #define NEXA_LOWB 1326 10 | #define NEXA_LOWC 2720 11 | #define NEXA_BURSTDELAY 10000 12 | 13 | void nexaTxSymbol(uint8_t symbol); 14 | void nexaTxPacket(uint64_t *d, uint8_t dim_packet_flag, uint8_t rep); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /python/jsonrpclib/history.py: -------------------------------------------------------------------------------- 1 | class History(object): 2 | """ 3 | This holds all the response and request objects for a 4 | session. A server using this should call "clear" after 5 | each request cycle in order to keep it from clogging 6 | memory. 7 | """ 8 | requests = [] 9 | responses = [] 10 | _instance = None 11 | 12 | @classmethod 13 | def instance(cls): 14 | if not cls._instance: 15 | cls._instance = cls() 16 | return cls._instance 17 | 18 | def add_response(self, response_obj): 19 | self.responses.append(response_obj) 20 | 21 | def add_request(self, request_obj): 22 | self.requests.append(request_obj) 23 | 24 | @property 25 | def request(self): 26 | if len(self.requests) == 0: 27 | return None 28 | else: 29 | return self.requests[-1] 30 | 31 | @property 32 | def response(self): 33 | if len(self.responses) == 0: 34 | return None 35 | else: 36 | return self.responses[-1] 37 | 38 | def clear(self): 39 | del self.requests[:] 40 | del self.responses[:] 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pihat 2 | ===== 3 | This is cloned from original pihat http://www.skagmo.com/page.php?p=projects/22_pihat 4 | 5 | Status support is added according to http://www.picaxeforum.co.uk/showthread.php?16509-El-cheapo-mains-remote&p=147355&viewfull=1#post147355 6 | 7 | to run it download pihat to your pi and run 8 | 9 | chmod +x ./pihat 10 | 11 | To pair 12 | ./pihat --brand=5 --repeats=50 --id=0 --channel=7 --state=1 13 | 14 | To turn on 15 | ./pihat --brand=5 --id=0 --channel=7 --state=1 16 | 17 | To turn off 18 | ./pihat --brand=5 --id=0 --channel=7 --state=0 19 | 20 | 21 | id is the remote id between 0 - 262143. 65536 - 131071 for Morrison remote and 196608 - 262143 for Status remote. 22 | 23 | State takes value from 0 (off) to 1 (on). Note for ALL button (aka channel 0) either states will results off action since that's there is no ALL On button on Status Remotes. 24 | 25 | channel takes value from 0 to 7 with the following mappings: 26 | 27 | 0 -> ALL 28 | 29 | 7 -> button 1 30 | 31 | 6 -> button 4 32 | 33 | 5 -> button 3 34 | 35 | 3 -> button 2 36 | 37 | If you want to use your FM radio to record the transmission you can specify --frequency=1 to the above commands and turn your radio to 100 MHz. 38 | -------------------------------------------------------------------------------- /python/jsonrpclib/config.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | class LocalClasses(dict): 4 | def add(self, cls): 5 | self[cls.__name__] = cls 6 | 7 | class Config(object): 8 | """ 9 | This is pretty much used exclusively for the 'jsonclass' 10 | functionality... set use_jsonclass to False to turn it off. 11 | You can change serialize_method and ignore_attribute, or use 12 | the local_classes.add(class) to include "local" classes. 13 | """ 14 | use_jsonclass = True 15 | # Change to False to keep __jsonclass__ entries raw. 16 | serialize_method = '_serialize' 17 | # The serialize_method should be a string that references the 18 | # method on a custom class object which is responsible for 19 | # returning a tuple of the constructor arguments and a dict of 20 | # attributes. 21 | ignore_attribute = '_ignore' 22 | # The ignore attribute should be a string that references the 23 | # attribute on a custom class object which holds strings and / or 24 | # references of the attributes the class translator should ignore. 25 | classes = LocalClasses() 26 | # The list of classes to use for jsonclass translation. 27 | version = 2.0 28 | # Version of the JSON-RPC spec to support 29 | user_agent = 'jsonrpclib/0.1 (Python %s)' % \ 30 | '.'.join([str(ver) for ver in sys.version_info[0:3]]) 31 | # User agent to use for calls. 32 | _instance = None 33 | 34 | @classmethod 35 | def instance(cls): 36 | if not cls._instance: 37 | cls._instance = cls() 38 | return cls._instance 39 | -------------------------------------------------------------------------------- /status.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************/ 2 | /* PiHAT routines for Status remote power switches */ 3 | /* */ 4 | /* S7mx1, 2012 */ 5 | /******************************************************************************/ 6 | 7 | #include "status.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include "radio.h" 13 | 14 | /* Status has 2 different symbols, 0, 1 */ 15 | void statusTxSymbol(uint8_t symbol){ 16 | if (symbol == 0) { 17 | askHigh(); 18 | usleep(STATUS_SHORT); 19 | askLow(); 20 | usleep(STATUS_LONG); 21 | } 22 | else { 23 | askHigh(); 24 | usleep(STATUS_LONG); 25 | askLow(); 26 | usleep(STATUS_SHORT); 27 | } 28 | } 29 | 30 | void statusTxPacket(uint64_t *d, uint8_t rep){ 31 | uint64_t mask; 32 | uint8_t symbol_nr; 33 | uint8_t symbol_nr_max; 34 | uint8_t p_nr; 35 | 36 | symbol_nr_max = 25; 37 | 38 | for (p_nr = 0; p_nr < rep; p_nr++){ 39 | 40 | mask = 0x1000000; 41 | 42 | /* Transmit the packet content */ 43 | for (symbol_nr = 0; symbol_nr>= 1; 47 | } 48 | usleep(STATUS_BURSTDELAY); 49 | } 50 | usleep(4 * STATUS_BURSTDELAY); 51 | } 52 | -------------------------------------------------------------------------------- /python/pi-switch.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import logging.handlers 3 | from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer 4 | import subprocess 5 | 6 | 7 | def set_logger(): 8 | logger = logging.getLogger('') 9 | logger.setLevel(logging.DEBUG) 10 | 11 | console = logging.StreamHandler() 12 | console.setLevel(logging.DEBUG) 13 | console.setFormatter(logging.Formatter('[%(asctime)s] %(message)s')) 14 | 15 | 16 | hdlr=logging.handlers.SysLogHandler(address="/dev/log") 17 | hdlr.setFormatter(logging.Formatter('[%(asctime)s] %(message)s')) 18 | hdlr.setLevel(logging.DEBUG) 19 | logger.addHandler(hdlr) 20 | 21 | logger.addHandler(console) 22 | return logger 23 | 24 | 25 | def switch(parameters): 26 | command=["pihat"] 27 | for key in ("brand","channel","id","state","frequency"): 28 | command.append("--"+key+"="+parameters[key]) 29 | process=subprocess.Popen(command, shell = False ,stdin=subprocess.PIPE,stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 30 | messages=[] 31 | while True: 32 | next_line = process.stdout.readline() 33 | if next_line == '' and process.poll() != None: 34 | break 35 | if not next_line.strip(): 36 | continue 37 | else: 38 | messages.append(next_line) 39 | return (process.returncode,messages) 40 | 41 | 42 | if __name__ == '__main__': 43 | global logger 44 | logger = set_logger() 45 | logger.debug('Running JSON-RPC server on port 8888') 46 | server = SimpleJSONRPCServer(('0.0.0.0', int(8888)),logger) 47 | server.register_introspection_functions() 48 | server.register_function(switch) 49 | server.serve_forever() 50 | -------------------------------------------------------------------------------- /radio.h: -------------------------------------------------------------------------------- 1 | #ifndef _RADIO_H_ 2 | #define _RADIO_H_ 3 | 4 | #define BCM2708_PERI_BASE 0x20000000 5 | #define GPIO_BASE (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */ 6 | 7 | #define PAGE_SIZE (4*1024) 8 | #define BLOCK_SIZE (4*1024) 9 | 10 | int mem_fd; 11 | char *gpio_mem, *gpio_map; 12 | char *spi0_mem, *spi0_map; 13 | 14 | // I/O access 15 | volatile unsigned *gpio; 16 | volatile unsigned *allof7e; 17 | 18 | // GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x) or SET_GPIO_ALT(x,y) 19 | #define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3)) 20 | #define OUT_GPIO(g) *(gpio+((g)/10)) |= (1<<(((g)%10)*3)) 21 | #define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3)) 22 | 23 | #define GPIO_SET *(gpio+7) // sets bits which are 1 ignores bits which are 0 24 | #define GPIO_CLR *(gpio+10) // clears bits which are 1 ignores bits which are 0 25 | #define GPIO_GET *(gpio+13) // sets bits which are 1 ignores bits which are 0 26 | 27 | #define ACCESS(base) *(volatile int*)((int)allof7e+base-0x7e000000) 28 | #define SETBIT(base, bit) ACCESS(base) |= 1< 10 | #include 11 | #include 12 | #include "radio.h" 13 | 14 | /* Nexa has three different symbols, 0, 1 and 2, where 2 is used only for the special dim-packet */ 15 | void nexaTxSymbol(uint8_t symbol){ 16 | askHigh(); 17 | usleep(NEXA_HIGH); 18 | 19 | askLow(); 20 | if (symbol == 1) usleep(NEXA_LOWB); 21 | else usleep(NEXA_LOWA); 22 | 23 | askHigh(); 24 | usleep(NEXA_HIGH); 25 | 26 | askLow(); 27 | if (symbol == 2) usleep(NEXA_LOWA); // Send special dim-symbol 28 | else if (symbol == 1) usleep(NEXA_LOWA); 29 | else usleep(NEXA_LOWB); 30 | } 31 | 32 | void nexaTxPacket(uint64_t *d, uint8_t dim_packet_flag, uint8_t rep){ 33 | uint64_t mask; 34 | uint8_t symbol_nr; 35 | uint8_t symbol_nr_max; 36 | uint8_t p_nr; 37 | 38 | if (dim_packet_flag) symbol_nr_max = 36; 39 | else symbol_nr_max = 32; 40 | 41 | for (p_nr = 0; p_nr < rep; p_nr++){ 42 | /* Preamble */ 43 | askHigh(); 44 | usleep(NEXA_HIGH); 45 | askLow(); 46 | usleep(NEXA_LOWC); 47 | 48 | mask = 0x800000000; 49 | 50 | /* Transmit the packet content */ 51 | for (symbol_nr = 0; symbol_nr>= 1; 56 | } 57 | 58 | /* "Postamble" */ 59 | askHigh(); 60 | usleep(NEXA_HIGH); 61 | askLow(); 62 | usleep(NEXA_BURSTDELAY); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /radio.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************/ 2 | /* Fractional-N synthesizer setup */ 3 | /* */ 4 | /* This code is nearly a direct copy of the PiFm-project */ 5 | /* by Oliver Mattos and Oskar Weigl. */ 6 | /* */ 7 | /* Jon Petter Skagmo, 2012 */ 8 | /******************************************************************************/ 9 | 10 | #include "radio.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | void setup_fm(){ 24 | allof7e = (unsigned *)mmap( 25 | NULL, 26 | 0x01000000, //len 27 | PROT_READ|PROT_WRITE, 28 | MAP_SHARED, 29 | mem_fd, 30 | 0x20000000 //base 31 | ); 32 | 33 | if ((int)allof7e==-1) exit(-1); 34 | 35 | SETBIT(GPFSEL0 , 14); 36 | CLRBIT(GPFSEL0 , 13); 37 | CLRBIT(GPFSEL0 , 12); 38 | 39 | struct GPCTL setupword = {6/*SRC*/, 1, 0, 0, 0, 1,0x5a}; 40 | 41 | ACCESS(CM_GP0CTL) = *((int*)&setupword); 42 | } 43 | 44 | // 45 | // Set up a memory regions to access GPIO 46 | // 47 | void setup_io(){ 48 | /* open /dev/mem */ 49 | if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) { 50 | printf("can't open /dev/mem \n"); 51 | exit (-1); 52 | } 53 | 54 | /* mmap GPIO */ 55 | 56 | // Allocate MAP block 57 | if ((gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) { 58 | printf("allocation error \n"); 59 | exit (-1); 60 | } 61 | 62 | // Make sure pointer is on 4K boundary 63 | if ((unsigned long)gpio_mem % PAGE_SIZE) 64 | gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE); 65 | 66 | // Now map it 67 | gpio_map = (unsigned char *)mmap( 68 | gpio_mem, 69 | BLOCK_SIZE, 70 | PROT_READ|PROT_WRITE, 71 | MAP_SHARED|MAP_FIXED, 72 | mem_fd, 73 | GPIO_BASE 74 | ); 75 | 76 | if ((long)gpio_map < 0) { 77 | printf("mmap error %d\n", (int)gpio_map); 78 | exit (-1); 79 | } 80 | 81 | // Always use volatile pointer! 82 | gpio = (volatile unsigned *)gpio_map; 83 | } // setup_io 84 | 85 | /* Added functions to enable and disable carrier */ 86 | 87 | void askHigh(){ 88 | struct GPCTL setupword = {6/*SRC*/, 1, 0, 0, 0, 1,0x5a}; // Set CM_GP0CTL.ENABLE to 1 89 | ACCESS(CM_GP0CTL) = *((int*)&setupword); 90 | } 91 | 92 | void askLow(){ 93 | struct GPCTL setupword = {6/*SRC*/, 0, 0, 0, 0, 1,0x5a}; // Set CM_GP0CTL.ENABLE to 0 94 | ACCESS(CM_GP0CTL) = *((int*)&setupword); 95 | } 96 | -------------------------------------------------------------------------------- /python/jsonrpclib/jsonclass.py: -------------------------------------------------------------------------------- 1 | import types 2 | import inspect 3 | import re 4 | import traceback 5 | 6 | from jsonrpclib import config 7 | 8 | iter_types = [ 9 | types.DictType, 10 | types.ListType, 11 | types.TupleType 12 | ] 13 | 14 | string_types = [ 15 | types.StringType, 16 | types.UnicodeType 17 | ] 18 | 19 | numeric_types = [ 20 | types.IntType, 21 | types.LongType, 22 | types.FloatType 23 | ] 24 | 25 | value_types = [ 26 | types.BooleanType, 27 | types.NoneType 28 | ] 29 | 30 | supported_types = iter_types+string_types+numeric_types+value_types 31 | invalid_module_chars = r'[^a-zA-Z0-9\_\.]' 32 | 33 | class TranslationError(Exception): 34 | pass 35 | 36 | def dump(obj, serialize_method=None, ignore_attribute=None, ignore=[]): 37 | if not serialize_method: 38 | serialize_method = config.serialize_method 39 | if not ignore_attribute: 40 | ignore_attribute = config.ignore_attribute 41 | obj_type = type(obj) 42 | # Parse / return default "types"... 43 | if obj_type in numeric_types+string_types+value_types: 44 | return obj 45 | if obj_type in iter_types: 46 | if obj_type in (types.ListType, types.TupleType): 47 | new_obj = [] 48 | for item in obj: 49 | new_obj.append(dump(item, serialize_method, 50 | ignore_attribute, ignore)) 51 | if obj_type is types.TupleType: 52 | new_obj = tuple(new_obj) 53 | return new_obj 54 | # It's a dict... 55 | else: 56 | new_obj = {} 57 | for key, value in obj.iteritems(): 58 | new_obj[key] = dump(value, serialize_method, 59 | ignore_attribute, ignore) 60 | return new_obj 61 | # It's not a standard type, so it needs __jsonclass__ 62 | module_name = inspect.getmodule(obj).__name__ 63 | class_name = obj.__class__.__name__ 64 | json_class = class_name 65 | if module_name not in ['', '__main__']: 66 | json_class = '%s.%s' % (module_name, json_class) 67 | return_obj = {"__jsonclass__":[json_class,]} 68 | # If a serialization method is defined.. 69 | if serialize_method in dir(obj): 70 | # Params can be a dict (keyword) or list (positional) 71 | # Attrs MUST be a dict. 72 | serialize = getattr(obj, serialize_method) 73 | params, attrs = serialize() 74 | return_obj['__jsonclass__'].append(params) 75 | return_obj.update(attrs) 76 | return return_obj 77 | # Otherwise, try to figure it out 78 | # Obviously, we can't assume to know anything about the 79 | # parameters passed to __init__ 80 | return_obj['__jsonclass__'].append([]) 81 | attrs = {} 82 | ignore_list = getattr(obj, ignore_attribute, [])+ignore 83 | for attr_name, attr_value in obj.__dict__.iteritems(): 84 | if type(attr_value) in supported_types and \ 85 | attr_name not in ignore_list and \ 86 | attr_value not in ignore_list: 87 | attrs[attr_name] = dump(attr_value, serialize_method, 88 | ignore_attribute, ignore) 89 | return_obj.update(attrs) 90 | return return_obj 91 | 92 | def load(obj): 93 | if type(obj) in string_types+numeric_types+value_types: 94 | return obj 95 | if type(obj) is types.ListType: 96 | return_list = [] 97 | for entry in obj: 98 | return_list.append(load(entry)) 99 | return return_list 100 | # Othewise, it's a dict type 101 | if '__jsonclass__' not in obj.keys(): 102 | return_dict = {} 103 | for key, value in obj.iteritems(): 104 | new_value = load(value) 105 | return_dict[key] = new_value 106 | return return_dict 107 | # It's a dict, and it's a __jsonclass__ 108 | orig_module_name = obj['__jsonclass__'][0] 109 | params = obj['__jsonclass__'][1] 110 | if orig_module_name == '': 111 | raise TranslationError('Module name empty.') 112 | json_module_clean = re.sub(invalid_module_chars, '', orig_module_name) 113 | if json_module_clean != orig_module_name: 114 | raise TranslationError('Module name %s has invalid characters.' % 115 | orig_module_name) 116 | json_module_parts = json_module_clean.split('.') 117 | json_class = None 118 | if len(json_module_parts) == 1: 119 | # Local class name -- probably means it won't work 120 | if json_module_parts[0] not in config.classes.keys(): 121 | raise TranslationError('Unknown class or module %s.' % 122 | json_module_parts[0]) 123 | json_class = config.classes[json_module_parts[0]] 124 | else: 125 | json_class_name = json_module_parts.pop() 126 | json_module_tree = '.'.join(json_module_parts) 127 | try: 128 | temp_module = __import__(json_module_tree) 129 | except ImportError: 130 | raise TranslationError('Could not import %s from module %s.' % 131 | (json_class_name, json_module_tree)) 132 | json_class = getattr(temp_module, json_class_name) 133 | # Creating the object... 134 | new_obj = None 135 | if type(params) is types.ListType: 136 | new_obj = json_class(*params) 137 | elif type(params) is types.DictType: 138 | new_obj = json_class(**params) 139 | else: 140 | raise TranslationError('Constructor args must be a dict or list.') 141 | for key, value in obj.iteritems(): 142 | if key == '__jsonclass__': 143 | continue 144 | setattr(new_obj, key, value) 145 | return new_obj 146 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************/ 2 | /* PiHAT - A software based home automation transmitter for the Raspberry PI */ 3 | /* */ 4 | /* Based on the work of the PiFM-guys, PiHAT enables you to control wireless */ 5 | /* remote power switches with a Raspberry Pi with no extra hardware! */ 6 | /* For proof of concept, simply connect a wire to GPIO 4, */ 7 | /* but for long term use a 433.92 MHz band pass filter _must_ be used! */ 8 | /* */ 9 | /* Use of this application is solely at your own risk! */ 10 | /* */ 11 | /* Original PiFm-project: http://www.icrobotics.co.uk/wiki/index.php/ */ 12 | /* Turning_the_Raspberry_Pi_Into_an_FM_Transmitter */ 13 | /* */ 14 | /* PiHAT project: http://skagmo.com -> Projects -> PiHAT */ 15 | /* */ 16 | /* Jon Petter Skagmo, 2012 */ 17 | /******************************************************************************/ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #define BRAND_NEXA 0x01 25 | #define BRAND_EVERFLOURISH 0x02 26 | #define BRAND_WAVEMAN 0x03 27 | #define BRAND_KANGHTAI 0x04 28 | #define BRAND_STATUS 0x05 29 | 30 | /***** Argp configuration start *****/ 31 | 32 | const char *argp_program_version = "PiHAT 0.2"; 33 | const char *argp_program_bug_address = ""; 34 | static char doc[] = "PiHAT -- Raspberry Pi Home Automation Transmitter."; 35 | static char args_doc[] = ""; 36 | 37 | struct arguments{ 38 | uint8_t brand; 39 | uint32_t id; 40 | uint8_t state; 41 | uint8_t channel; 42 | uint8_t group; 43 | uint8_t dim_level; 44 | uint8_t repeats; 45 | uint16_t frequency; 46 | }; 47 | 48 | static struct argp_option options[] = { 49 | {"brand", 'b', "BRAND", 0, "Brand of the receiver - defaults to '0' (nexa), '5' (Status)"}, 50 | {"id", 'i', "ID_FIELD", 0, "The unique ID field (integer)"}, 51 | {"state", 's', "STATE", 0, "Power state: '0', '1'"}, 52 | {"channel", 'c', "CHANNEL", 0, "Channel: '0' - '15'"}, 53 | {"group", 'g', "GROUP_ON", 0, "Set group-flag: '0', '1'"}, 54 | {"dim", 'd', "DIM_LEVEL", 0, "Dim-level for Nexa: '0' - '15'"}, 55 | {"repeats", 'r', "REPEAT_NR", 0, "Number of times to resend packet: '0' - '255'"}, 56 | {"frequency", 'f', "FREQUENCY", 0, "frequency: '0' (144.64 MHz), '1' (100 MHz)"}, 57 | {0} 58 | }; 59 | 60 | /* Option parser */ 61 | static error_t parse_opt (int key, char *arg, struct argp_state *state){ 62 | struct arguments *arguments = state->input; 63 | char* end; // Used to indicate if ASCII to int was successful 64 | 65 | switch (key){ 66 | /* Brand */ 67 | case 'b': 68 | arguments->brand = strtol(arg, &end, 16); 69 | if ( (*end) || (arguments->channel > 5) ) argp_usage(state); 70 | break; 71 | /* ID */ 72 | case 'i': 73 | arguments->id = strtol(arg, &end, 10); // Convert ASCII-number to integer 74 | if (*end) argp_usage(state); 75 | break; 76 | /* State */ 77 | case 's': 78 | if (!strcmp(arg, "1")) arguments->state = 1; 79 | else if (!strcmp(arg, "0")) arguments->state = 0; 80 | else argp_usage(state); 81 | break; 82 | /* Channel */ 83 | case 'c': 84 | arguments->channel = strtol(arg, &end, 10); 85 | if ( (*end) || (arguments->channel > 15) ) argp_usage(state); 86 | break; 87 | /* Group enable */ 88 | case 'g': 89 | if (!strcmp(arg, "1")) arguments->group = 1; 90 | else if (!strcmp(arg, "0")) arguments->group = 0; 91 | else argp_usage(state); 92 | break; 93 | /* Dim-level */ 94 | case 'd': 95 | arguments->dim_level = strtol(arg, &end, 10); 96 | if ( (*end) || (arguments->dim_level > 15) ) argp_usage(state); 97 | break; 98 | /* Number of repetitions */ 99 | case 'r': 100 | arguments->repeats = strtol(arg, &end, 10); 101 | if (*end) argp_usage(state); 102 | break; 103 | /* Frequency*/ 104 | case 'f': 105 | if (!strcmp(arg, "1")) arguments->frequency = 0x5000; 106 | else arguments->frequency = 0x374F; 107 | break; 108 | default: 109 | return ARGP_ERR_UNKNOWN; 110 | } 111 | return 0; 112 | } 113 | 114 | static struct argp argp = {options, parse_opt, args_doc, doc}; 115 | 116 | /***** ARGP configuration stop *****/ 117 | 118 | #include "radio.h" 119 | #include "nexa.h" 120 | #include "status.h" 121 | 122 | int main (int argc, char **argv){ 123 | struct arguments arguments; 124 | 125 | /* Set argument defaults */ 126 | arguments.brand = BRAND_NEXA; 127 | arguments.id = 0; 128 | arguments.state = 0; 129 | arguments.channel = 0; 130 | arguments.group = 0; 131 | arguments.dim_level = 0x10; // Invalid entry - to determine if dim_level has been set at a later time 132 | arguments.repeats = 3; 133 | arguments.frequency = 0x374F; 134 | 135 | /* Parse arguments */ 136 | argp_parse (&argp, argc, argv, 0, 0, &arguments); 137 | 138 | /* Data variables for RF-packets */ 139 | uint64_t data; // General purpose data variable 140 | 141 | /* Setup RF-configuration */ 142 | setup_io(); 143 | setup_fm(); 144 | if (arguments.frequency == 0x374F) 145 | printf("Tune to 144.64 MHz\n"); 146 | if (arguments.frequency == 0x5000) 147 | printf("Tune to 100 MHz\n"); 148 | ACCESS(CM_GP0DIV) = (0x5a << 24) + arguments.frequency; // Tune to 144.64 MHz to get the third harmonic at 433.92 MHz 149 | 150 | switch(arguments.brand){ 151 | case BRAND_NEXA: 152 | /* Fill the data-variable and call the TX-function */ 153 | data = 0; 154 | data |= ((uint64_t)arguments.id) << 10; 155 | data |= ((uint64_t)arguments.group) << 9; 156 | data |= ((uint64_t)arguments.state) << 8; 157 | data |= arguments.channel << 4; 158 | if (arguments.dim_level != 0x10) data |= arguments.dim_level; 159 | nexaTxPacket(&data, (arguments.dim_level != 0x10), arguments.repeats); 160 | break; 161 | case BRAND_STATUS: 162 | data = 0; 163 | data |= ((uint64_t)arguments.id) << 5; 164 | if ( arguments.channel >= 7 ) 165 | data |= ((uint64_t)7) << 1; 166 | else 167 | data |= ((uint64_t)arguments.channel) << 1; 168 | if ( arguments.channel == 0) 169 | data |= ((uint64_t)1) << 4; 170 | else 171 | data |= ((uint64_t)arguments.state) << 4; 172 | uint64_t mask; 173 | uint8_t symbol_nr; 174 | uint8_t symbol_nr_max; 175 | 176 | symbol_nr_max = 25; 177 | 178 | mask = 0x1000000; 179 | 180 | for (symbol_nr = 0; symbol_nr>= 1; 184 | } 185 | printf("\n"); 186 | statusTxPacket(&data, arguments.repeats); 187 | break; 188 | } 189 | 190 | return 0; 191 | } 192 | 193 | -------------------------------------------------------------------------------- /python/jsonrpclib/SimpleJSONRPCServer.py: -------------------------------------------------------------------------------- 1 | import jsonrpclib 2 | from jsonrpclib import Fault 3 | import SimpleXMLRPCServer 4 | import SocketServer 5 | import types 6 | import traceback 7 | import sys 8 | try: 9 | import fcntl 10 | except ImportError: 11 | # For Windows 12 | fcntl = None 13 | 14 | def get_version(request): 15 | # must be a dict 16 | if 'jsonrpc' in request.keys(): 17 | return 2.0 18 | if 'id' in request.keys(): 19 | return 1.0 20 | return None 21 | 22 | def validate_request(request): 23 | if type(request) is not types.DictType: 24 | fault = Fault( 25 | -32600, 'Request must be {}, not %s.' % type(request) 26 | ) 27 | return fault 28 | rpcid = request.get('id', None) 29 | version = get_version(request) 30 | if not version: 31 | fault = Fault(-32600, 'Request %s invalid.' % request, rpcid=rpcid) 32 | return fault 33 | request.setdefault('params', []) 34 | method = request.get('method', None) 35 | params = request.get('params') 36 | param_types = (types.ListType, types.DictType, types.TupleType) 37 | if not method or type(method) not in types.StringTypes or \ 38 | type(params) not in param_types: 39 | fault = Fault( 40 | -32600, 'Invalid request parameters or method.', rpcid=rpcid 41 | ) 42 | return fault 43 | return True 44 | 45 | class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher): 46 | 47 | def __init__(self, encoding=None): 48 | SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, 49 | allow_none=True, 50 | encoding=encoding) 51 | 52 | def _marshaled_dispatch(self, data, dispatch_method = None): 53 | response = None 54 | try: 55 | request = jsonrpclib.loads(data) 56 | except Exception, e: 57 | fault = Fault(-32700, 'Request %s invalid. (%s)' % (data, e)) 58 | response = fault.response() 59 | return response 60 | if not request: 61 | fault = Fault(-32600, 'Request invalid -- no request data.') 62 | return fault.response() 63 | if type(request) is types.ListType: 64 | # This SHOULD be a batch, by spec 65 | responses = [] 66 | for req_entry in request: 67 | result = validate_request(req_entry) 68 | if type(result) is Fault: 69 | responses.append(result.response()) 70 | continue 71 | resp_entry = self._marshaled_single_dispatch(req_entry) 72 | if resp_entry is not None: 73 | responses.append(resp_entry) 74 | if len(responses) > 0: 75 | response = '[%s]' % ','.join(responses) 76 | else: 77 | response = '' 78 | else: 79 | result = validate_request(request) 80 | if type(result) is Fault: 81 | return result.response() 82 | response = self._marshaled_single_dispatch(request) 83 | return response 84 | 85 | def _marshaled_single_dispatch(self, request): 86 | # TODO - Use the multiprocessing and skip the response if 87 | # it is a notification 88 | # Put in support for custom dispatcher here 89 | # (See SimpleXMLRPCServer._marshaled_dispatch) 90 | method = request.get('method') 91 | params = request.get('params') 92 | try: 93 | response = self._dispatch(method, params) 94 | except: 95 | exc_type, exc_value, exc_tb = sys.exc_info() 96 | fault = Fault(-32603, '%s:%s' % (exc_type, exc_value)) 97 | return fault.response() 98 | if 'id' not in request.keys() or request['id'] == None: 99 | # It's a notification 100 | return None 101 | try: 102 | response = jsonrpclib.dumps(response, 103 | methodresponse=True, 104 | rpcid=request['id'] 105 | ) 106 | return response 107 | except: 108 | exc_type, exc_value, exc_tb = sys.exc_info() 109 | fault = Fault(-32603, '%s:%s' % (exc_type, exc_value)) 110 | return fault.response() 111 | 112 | def _dispatch(self, method, params): 113 | func = None 114 | try: 115 | func = self.funcs[method] 116 | except KeyError: 117 | if self.instance is not None: 118 | if hasattr(self.instance, '_dispatch'): 119 | return self.instance._dispatch(method, params) 120 | else: 121 | try: 122 | func = SimpleXMLRPCServer.resolve_dotted_attribute( 123 | self.instance, 124 | method, 125 | True 126 | ) 127 | except AttributeError: 128 | pass 129 | if func is not None: 130 | try: 131 | if type(params) is types.ListType: 132 | response = func(*params) 133 | else: 134 | response = func(**params) 135 | return response 136 | except TypeError: 137 | return Fault(-32602, 'Invalid parameters.') 138 | except: 139 | err_lines = traceback.format_exc().splitlines() 140 | trace_string = '%s | %s' % (err_lines[-3], err_lines[-1]) 141 | fault = jsonrpclib.Fault(-32603, 'Server error: %s' % 142 | trace_string) 143 | return fault 144 | else: 145 | return Fault(-32601, 'Method %s not supported.' % method) 146 | 147 | class SimpleJSONRPCRequestHandler( 148 | SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): 149 | 150 | def do_POST(self): 151 | if not self.is_rpc_path_valid(): 152 | self.report_404() 153 | return 154 | try: 155 | max_chunk_size = 10*1024*1024 156 | size_remaining = int(self.headers["content-length"]) 157 | L = [] 158 | while size_remaining: 159 | chunk_size = min(size_remaining, max_chunk_size) 160 | L.append(self.rfile.read(chunk_size)) 161 | size_remaining -= len(L[-1]) 162 | data = ''.join(L) 163 | 164 | global logger 165 | clientIP, port = self.client_address 166 | logger.debug('Post Request from Client %s:%s - \"POST / HTTP/1.0 200\" -' % (clientIP, port)) 167 | 168 | response = self.server._marshaled_dispatch(data) 169 | 170 | logger.debug('Client request: %s' % data) 171 | logger.debug('Server response: %s' % response) 172 | 173 | self.send_response(200) 174 | 175 | except Exception, e: 176 | self.send_response(500) 177 | err_lines = traceback.format_exc().splitlines() 178 | trace_string = '%s | %s' % (err_lines[-3], err_lines[-1]) 179 | fault = jsonrpclib.Fault(-32603, 'Server error: %s' % trace_string) 180 | response = fault.response() 181 | if response == None: 182 | response = '' 183 | self.send_header("Content-type", "application/json-rpc") 184 | self.send_header("Content-length", str(len(response))) 185 | self.end_headers() 186 | self.wfile.write(response) 187 | self.wfile.flush() 188 | self.connection.shutdown(1) 189 | 190 | class SimpleJSONRPCServer(SocketServer.TCPServer, SimpleJSONRPCDispatcher): 191 | 192 | allow_reuse_address = True 193 | 194 | def __init__(self, addr, mylogger, requestHandler=SimpleJSONRPCRequestHandler, 195 | logRequests=False, encoding=None, bind_and_activate=True): 196 | global logger 197 | logger = mylogger 198 | self.logRequests = logRequests 199 | SimpleJSONRPCDispatcher.__init__(self, encoding) 200 | # TCPServer.__init__ has an extra parameter on 2.6+, so 201 | # check Python version and decide on how to call it 202 | vi = sys.version_info 203 | # if python 2.5 and lower 204 | if vi[0] < 3 and vi[1] < 6: 205 | SocketServer.TCPServer.__init__(self, addr, requestHandler) 206 | else: 207 | SocketServer.TCPServer.__init__(self, addr, requestHandler, 208 | bind_and_activate) 209 | if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'): 210 | flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD) 211 | flags |= fcntl.FD_CLOEXEC 212 | fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags) 213 | 214 | class CGIJSONRPCRequestHandler(SimpleJSONRPCDispatcher): 215 | 216 | def __init__(self, encoding=None): 217 | SimpleJSONRPCDispatcher.__init__(self, encoding) 218 | 219 | def handle_jsonrpc(self, request_text): 220 | response = self._marshaled_dispatch(request_text) 221 | print 'Content-Type: application/json-rpc' 222 | print 'Content-Length: %d' % len(response) 223 | print 224 | global logger 225 | logger.debug(response) 226 | sys.stdout.write(response) 227 | 228 | handle_xmlrpc = handle_jsonrpc 229 | -------------------------------------------------------------------------------- /python/jsonrpclib/jsonrpc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2009 Josh Marshall 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | ============================ 16 | JSONRPC Library (jsonrpclib) 17 | ============================ 18 | 19 | This library is a JSON-RPC v.2 (proposed) implementation which 20 | follows the xmlrpclib API for portability between clients. It 21 | uses the same Server / ServerProxy, loads, dumps, etc. syntax, 22 | while providing features not present in XML-RPC like: 23 | 24 | * Keyword arguments 25 | * Notifications 26 | * Versioning 27 | * Batches and batch notifications 28 | 29 | Eventually, I'll add a SimpleXMLRPCServer compatible library, 30 | and other things to tie the thing off nicely. :) 31 | 32 | For a quick-start, just open a console and type the following, 33 | replacing the server address, method, and parameters 34 | appropriately. 35 | >>> import jsonrpclib 36 | >>> server = jsonrpclib.Server('http://localhost:8181') 37 | >>> server.add(5, 6) 38 | 11 39 | >>> server._notify.add(5, 6) 40 | >>> batch = jsonrpclib.MultiCall(server) 41 | >>> batch.add(3, 50) 42 | >>> batch.add(2, 3) 43 | >>> batch._notify.add(3, 5) 44 | >>> batch() 45 | [53, 5] 46 | 47 | See http://code.google.com/p/jsonrpclib/ for more info. 48 | """ 49 | 50 | import types 51 | import sys 52 | from xmlrpclib import Transport as XMLTransport 53 | from xmlrpclib import SafeTransport as XMLSafeTransport 54 | from xmlrpclib import ServerProxy as XMLServerProxy 55 | from xmlrpclib import _Method as XML_Method 56 | import time 57 | 58 | # Library includes 59 | import jsonrpclib 60 | from jsonrpclib import config 61 | from jsonrpclib import history 62 | 63 | # JSON library importing 64 | cjson = None 65 | json = None 66 | try: 67 | import cjson 68 | except ImportError: 69 | pass 70 | if not cjson: 71 | try: 72 | import json 73 | except ImportError: 74 | pass 75 | if not cjson and not json: 76 | try: 77 | import simplejson as json 78 | except ImportError: 79 | raise ImportError('You must have the cjson, json, or simplejson ' + 80 | 'module(s) available.') 81 | 82 | #JSON Abstractions 83 | 84 | def jdumps(obj, encoding='utf-8'): 85 | # Do 'serialize' test at some point for other classes 86 | global cjson 87 | if cjson: 88 | return cjson.encode(obj) 89 | else: 90 | return json.dumps(obj, encoding=encoding) 91 | 92 | def jloads(json_string): 93 | global cjson 94 | if cjson: 95 | return cjson.decode(json_string) 96 | else: 97 | return json.loads(json_string) 98 | 99 | 100 | # XMLRPClib re-implemntations 101 | 102 | class ProtocolError(Exception): 103 | pass 104 | 105 | class TransportMixIn(object): 106 | """ Just extends the XMLRPC transport where necessary. """ 107 | user_agent = config.user_agent 108 | # for Python 2.7 support 109 | _connection = None 110 | 111 | def send_content(self, connection, request_body): 112 | connection.putheader("Content-Type", "application/json-rpc") 113 | connection.putheader("Content-Length", str(len(request_body))) 114 | connection.endheaders() 115 | if request_body: 116 | connection.send(request_body) 117 | 118 | def getparser(self): 119 | target = JSONTarget() 120 | return JSONParser(target), target 121 | 122 | class JSONParser(object): 123 | def __init__(self, target): 124 | self.target = target 125 | 126 | def feed(self, data): 127 | self.target.feed(data) 128 | 129 | def close(self): 130 | pass 131 | 132 | class JSONTarget(object): 133 | def __init__(self): 134 | self.data = [] 135 | 136 | def feed(self, data): 137 | self.data.append(data) 138 | 139 | def close(self): 140 | return ''.join(self.data) 141 | 142 | class Transport(TransportMixIn, XMLTransport): 143 | pass 144 | 145 | class SafeTransport(TransportMixIn, XMLSafeTransport): 146 | pass 147 | 148 | class ServerProxy(XMLServerProxy): 149 | """ 150 | Unfortunately, much more of this class has to be copied since 151 | so much of it does the serialization. 152 | """ 153 | 154 | def __init__(self, uri, transport=None, encoding=None, 155 | verbose=0, version=None): 156 | import urllib 157 | if not version: 158 | version = config.version 159 | self.__version = version 160 | schema, uri = urllib.splittype(uri) 161 | if schema not in ('http', 'https'): 162 | raise IOError('Unsupported JSON-RPC protocol.') 163 | self.__host, self.__handler = urllib.splithost(uri) 164 | if not self.__handler: 165 | # Not sure if this is in the JSON spec? 166 | #self.__handler = '/' 167 | self.__handler == '/' 168 | if transport is None: 169 | if schema == 'https': 170 | transport = SafeTransport() 171 | else: 172 | transport = Transport() 173 | self.__transport = transport 174 | self.__encoding = encoding 175 | self.__verbose = verbose 176 | 177 | def _request(self, methodname, params, rpcid=None): 178 | request = dumps(params, methodname, encoding=self.__encoding, 179 | rpcid=rpcid, version=self.__version) 180 | response = self._run_request(request) 181 | check_for_errors(response) 182 | return response['result'] 183 | 184 | def _request_notify(self, methodname, params, rpcid=None): 185 | request = dumps(params, methodname, encoding=self.__encoding, 186 | rpcid=rpcid, version=self.__version, notify=True) 187 | response = self._run_request(request, notify=True) 188 | check_for_errors(response) 189 | return 190 | 191 | def _run_request(self, request, notify=None): 192 | history.add_request(request) 193 | 194 | response = self.__transport.request( 195 | self.__host, 196 | self.__handler, 197 | request, 198 | verbose=self.__verbose 199 | ) 200 | 201 | # Here, the XMLRPC library translates a single list 202 | # response to the single value -- should we do the 203 | # same, and require a tuple / list to be passed to 204 | # the response object, or expect the Server to be 205 | # outputting the response appropriately? 206 | 207 | history.add_response(response) 208 | if not response: 209 | return None 210 | return_obj = loads(response) 211 | return return_obj 212 | 213 | def __getattr__(self, name): 214 | # Same as original, just with new _Method reference 215 | return _Method(self._request, name) 216 | 217 | @property 218 | def _notify(self): 219 | # Just like __getattr__, but with notify namespace. 220 | return _Notify(self._request_notify) 221 | 222 | 223 | class _Method(XML_Method): 224 | 225 | def __call__(self, *args, **kwargs): 226 | if len(args) > 0 and len(kwargs) > 0: 227 | raise ProtocolError('Cannot use both positional ' + 228 | 'and keyword arguments (according to JSON-RPC spec.)') 229 | if len(args) > 0: 230 | return self.__send(self.__name, args) 231 | else: 232 | return self.__send(self.__name, kwargs) 233 | 234 | def __getattr__(self, name): 235 | self.__name = '%s.%s' % (self.__name, name) 236 | return self 237 | # The old method returned a new instance, but this seemed wasteful. 238 | # The only thing that changes is the name. 239 | #return _Method(self.__send, "%s.%s" % (self.__name, name)) 240 | 241 | class _Notify(object): 242 | def __init__(self, request): 243 | self._request = request 244 | 245 | def __getattr__(self, name): 246 | return _Method(self._request, name) 247 | 248 | # Batch implementation 249 | 250 | class MultiCallMethod(object): 251 | 252 | def __init__(self, method, notify=False): 253 | self.method = method 254 | self.params = [] 255 | self.notify = notify 256 | 257 | def __call__(self, *args, **kwargs): 258 | if len(kwargs) > 0 and len(args) > 0: 259 | raise ProtocolError('JSON-RPC does not support both ' + 260 | 'positional and keyword arguments.') 261 | if len(kwargs) > 0: 262 | self.params = kwargs 263 | else: 264 | self.params = args 265 | 266 | def request(self, encoding=None, rpcid=None): 267 | return dumps(self.params, self.method, version=2.0, 268 | encoding=encoding, rpcid=rpcid, notify=self.notify) 269 | 270 | def __repr__(self): 271 | return '%s' % self.request() 272 | 273 | def __getattr__(self, method): 274 | new_method = '%s.%s' % (self.method, method) 275 | self.method = new_method 276 | return self 277 | 278 | class MultiCallNotify(object): 279 | 280 | def __init__(self, multicall): 281 | self.multicall = multicall 282 | 283 | def __getattr__(self, name): 284 | new_job = MultiCallMethod(name, notify=True) 285 | self.multicall._job_list.append(new_job) 286 | return new_job 287 | 288 | class MultiCallIterator(object): 289 | 290 | def __init__(self, results): 291 | self.results = results 292 | 293 | def __iter__(self): 294 | for i in range(0, len(self.results)): 295 | yield self[i] 296 | raise StopIteration 297 | 298 | def __getitem__(self, i): 299 | item = self.results[i] 300 | check_for_errors(item) 301 | return item['result'] 302 | 303 | def __len__(self): 304 | return len(self.results) 305 | 306 | class MultiCall(object): 307 | 308 | def __init__(self, server): 309 | self._server = server 310 | self._job_list = [] 311 | 312 | def _request(self): 313 | if len(self._job_list) < 1: 314 | # Should we alert? This /is/ pretty obvious. 315 | return 316 | request_body = '[ %s ]' % ','.join([job.request() for 317 | job in self._job_list]) 318 | responses = self._server._run_request(request_body) 319 | del self._job_list[:] 320 | if not responses: 321 | responses = [] 322 | return MultiCallIterator(responses) 323 | 324 | @property 325 | def _notify(self): 326 | return MultiCallNotify(self) 327 | 328 | def __getattr__(self, name): 329 | new_job = MultiCallMethod(name) 330 | self._job_list.append(new_job) 331 | return new_job 332 | 333 | __call__ = _request 334 | 335 | # These lines conform to xmlrpclib's "compatibility" line. 336 | # Not really sure if we should include these, but oh well. 337 | Server = ServerProxy 338 | 339 | class Fault(object): 340 | # JSON-RPC error class 341 | def __init__(self, code=-32000, message='Server error', rpcid=None): 342 | self.faultCode = code 343 | self.faultString = message 344 | self.rpcid = rpcid 345 | 346 | def error(self): 347 | return {'code':self.faultCode, 'message':self.faultString} 348 | 349 | def response(self, rpcid=None, version=None): 350 | if not version: 351 | version = config.version 352 | if rpcid: 353 | self.rpcid = rpcid 354 | return dumps( 355 | self, methodresponse=True, rpcid=self.rpcid, version=version 356 | ) 357 | 358 | def __repr__(self): 359 | return '' % (self.faultCode, self.faultString) 360 | 361 | def random_id(length=8): 362 | import string 363 | import random 364 | random.seed() 365 | choices = string.lowercase+string.digits 366 | return_id = '' 367 | for i in range(length): 368 | return_id += random.choice(choices) 369 | return return_id 370 | 371 | class Payload(dict): 372 | def __init__(self, rpcid=None, version=None): 373 | if not version: 374 | version = config.version 375 | self.id = rpcid 376 | self.version = float(version) 377 | 378 | def request(self, method, params=[]): 379 | if type(method) not in types.StringTypes: 380 | raise ValueError('Method name must be a string.') 381 | if not self.id: 382 | self.id = random_id() 383 | request = { 'id':self.id, 'method':method } 384 | if params: 385 | request['params'] = params 386 | if self.version >= 2: 387 | request['jsonrpc'] = str(self.version) 388 | return request 389 | 390 | def notify(self, method, params=[]): 391 | request = self.request(method, params) 392 | if self.version >= 2: 393 | del request['id'] 394 | else: 395 | request['id'] = None 396 | return request 397 | 398 | def response(self, result=None): 399 | response = {'result':result, 'id':self.id} 400 | if self.version >= 2: 401 | response['jsonrpc'] = str(self.version) 402 | else: 403 | response['error'] = None 404 | return response 405 | 406 | def error(self, code=-32000, message='Server error.'): 407 | error = self.response() 408 | if self.version >= 2: 409 | del error['result'] 410 | else: 411 | error['result'] = None 412 | error['error'] = {'code':code, 'message':message} 413 | return error 414 | 415 | def dumps(params=[], methodname=None, methodresponse=None, 416 | encoding=None, rpcid=None, version=None, notify=None): 417 | """ 418 | This differs from the Python implementation in that it implements 419 | the rpcid argument since the 2.0 spec requires it for responses. 420 | """ 421 | if not version: 422 | version = config.version 423 | valid_params = (types.TupleType, types.ListType, types.DictType) 424 | if methodname in types.StringTypes and \ 425 | type(params) not in valid_params and \ 426 | not isinstance(params, Fault): 427 | """ 428 | If a method, and params are not in a listish or a Fault, 429 | error out. 430 | """ 431 | raise TypeError('Params must be a dict, list, tuple or Fault ' + 432 | 'instance.') 433 | # Begin parsing object 434 | payload = Payload(rpcid=rpcid, version=version) 435 | if not encoding: 436 | encoding = 'utf-8' 437 | if type(params) is Fault: 438 | response = payload.error(params.faultCode, params.faultString) 439 | return jdumps(response, encoding=encoding) 440 | if type(methodname) not in types.StringTypes and methodresponse != True: 441 | raise ValueError('Method name must be a string, or methodresponse '+ 442 | 'must be set to True.') 443 | if config.use_jsonclass == True: 444 | from jsonrpclib import jsonclass 445 | params = jsonclass.dump(params) 446 | if methodresponse is True: 447 | if rpcid is None: 448 | raise ValueError('A method response must have an rpcid.') 449 | response = payload.response(params) 450 | return jdumps(response, encoding=encoding) 451 | request = None 452 | if notify == True: 453 | request = payload.notify(methodname, params) 454 | else: 455 | request = payload.request(methodname, params) 456 | return jdumps(request, encoding=encoding) 457 | 458 | def loads(data): 459 | """ 460 | This differs from the Python implementation, in that it returns 461 | the request structure in Dict format instead of the method, params. 462 | It will return a list in the case of a batch request / response. 463 | """ 464 | if data == '': 465 | # notification 466 | return None 467 | result = jloads(data) 468 | # if the above raises an error, the implementing server code 469 | # should return something like the following: 470 | # { 'jsonrpc':'2.0', 'error': fault.error(), id: None } 471 | if config.use_jsonclass == True: 472 | from jsonrpclib import jsonclass 473 | result = jsonclass.load(result) 474 | return result 475 | 476 | def check_for_errors(result): 477 | if not result: 478 | # Notification 479 | return result 480 | if type(result) is not types.DictType: 481 | raise TypeError('Response is not a dict.') 482 | if 'jsonrpc' in result.keys() and float(result['jsonrpc']) > 2.0: 483 | raise NotImplementedError('JSON-RPC version not yet supported.') 484 | if 'result' not in result.keys() and 'error' not in result.keys(): 485 | raise ValueError('Response does not have a result or error key.') 486 | if 'error' in result.keys() and result['error'] != None: 487 | code = result['error']['code'] 488 | message = result['error']['message'] 489 | raise ProtocolError((code, message)) 490 | return result 491 | 492 | def isbatch(result): 493 | if type(result) not in (types.ListType, types.TupleType): 494 | return False 495 | if len(result) < 1: 496 | return False 497 | if type(result[0]) is not types.DictType: 498 | return False 499 | if 'jsonrpc' not in result[0].keys(): 500 | return False 501 | try: 502 | version = float(result[0]['jsonrpc']) 503 | except ValueError: 504 | raise ProtocolError('"jsonrpc" key must be a float(able) value.') 505 | if version < 2: 506 | return False 507 | return True 508 | 509 | def isnotification(request): 510 | if 'id' not in request.keys(): 511 | # 2.0 notification 512 | return True 513 | if request['id'] == None: 514 | # 1.0 notification 515 | return True 516 | return False 517 | --------------------------------------------------------------------------------