| /* |
| * ir-encode.c - encodes IR scancodes in different protocols |
| * |
| * Copyright (C) 2016 Sean Young <sean@mess.org> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, version 2 of the License. |
| |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| /* |
| * TODO: XMP protocol and MCE keyboard |
| */ |
| |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <ctype.h> |
| #include <stdio.h> |
| |
| #include <linux/lirc.h> |
| |
| #include "ir-encode.h" |
| |
| #define NS_TO_US(x) (((x)+500)/1000) |
| |
| static const int nec_unit = 562500; |
| |
| static void nec_add_byte(unsigned *buf, int *n, unsigned bits) |
| { |
| int i; |
| for (i=0; i<8; i++) { |
| buf[(*n)++] = NS_TO_US(nec_unit); |
| if (bits & (1 << i)) |
| buf[(*n)++] = NS_TO_US(nec_unit * 3); |
| else |
| buf[(*n)++] = NS_TO_US(nec_unit); |
| } |
| } |
| |
| static int nec_encode(enum rc_proto proto, unsigned scancode, unsigned *buf) |
| { |
| int n = 0; |
| |
| buf[n++] = NS_TO_US(nec_unit * 16); |
| buf[n++] = NS_TO_US(nec_unit * 8); |
| |
| switch (proto) { |
| default: |
| return 0; |
| case RC_PROTO_NEC: |
| nec_add_byte(buf, &n, scancode >> 8); |
| nec_add_byte(buf, &n, ~(scancode >> 8)); |
| nec_add_byte(buf, &n, scancode); |
| nec_add_byte(buf, &n, ~scancode); |
| break; |
| case RC_PROTO_NECX: |
| nec_add_byte(buf, &n, scancode >> 16); |
| nec_add_byte(buf, &n, scancode >> 8); |
| nec_add_byte(buf, &n, scancode); |
| nec_add_byte(buf, &n, ~scancode); |
| break; |
| case RC_PROTO_NEC32: |
| nec_add_byte(buf, &n, scancode >> 16); |
| nec_add_byte(buf, &n, scancode >> 24); |
| nec_add_byte(buf, &n, scancode); |
| nec_add_byte(buf, &n, scancode >> 8); |
| break; |
| } |
| |
| buf[n++] = NS_TO_US(nec_unit); |
| |
| return n; |
| } |
| |
| static int jvc_encode(enum rc_proto proto, unsigned scancode, unsigned *buf) |
| { |
| const int jvc_unit = 525000; |
| int i; |
| |
| /* swap bytes so address comes first */ |
| scancode = ((scancode << 8) & 0xff00) | ((scancode >> 8) & 0x00ff); |
| |
| *buf++ = NS_TO_US(jvc_unit * 16); |
| *buf++ = NS_TO_US(jvc_unit * 8); |
| |
| for (i=0; i<16; i++) { |
| *buf++ = NS_TO_US(jvc_unit); |
| |
| if (scancode & 1) |
| *buf++ = NS_TO_US(jvc_unit * 3); |
| else |
| *buf++ = NS_TO_US(jvc_unit); |
| |
| scancode >>= 1; |
| } |
| |
| *buf = NS_TO_US(jvc_unit); |
| |
| return 35; |
| } |
| |
| static const int sanyo_unit = 562500; |
| |
| static void sanyo_add_bits(unsigned **buf, int bits, int count) |
| { |
| int i; |
| for (i=0; i<count; i++) { |
| *(*buf)++ = NS_TO_US(sanyo_unit); |
| |
| if (bits & (1 << i)) |
| *(*buf)++ = NS_TO_US(sanyo_unit * 3); |
| else |
| *(*buf)++ = NS_TO_US(sanyo_unit); |
| } |
| } |
| |
| static int sanyo_encode(enum rc_proto proto, unsigned scancode, unsigned *buf) |
| { |
| *buf++ = NS_TO_US(sanyo_unit * 16); |
| *buf++ = NS_TO_US(sanyo_unit * 8); |
| |
| sanyo_add_bits(&buf, scancode >> 8, 13); |
| sanyo_add_bits(&buf, ~(scancode >> 8), 13); |
| sanyo_add_bits(&buf, scancode, 8); |
| sanyo_add_bits(&buf, ~scancode, 8); |
| |
| *buf = NS_TO_US(sanyo_unit); |
| |
| return 87; |
| } |
| |
| static const int sharp_unit = 40000; |
| |
| static void sharp_add_bits(unsigned **buf, int bits, int count) |
| { |
| int i; |
| for (i=0; i<count; i++) { |
| *(*buf)++ = NS_TO_US(sharp_unit * 8); |
| |
| if (bits & (1 << i)) |
| *(*buf)++ = NS_TO_US(sharp_unit * 50); |
| else |
| *(*buf)++ = NS_TO_US(sharp_unit * 25); |
| } |
| } |
| |
| static int sharp_encode(enum rc_proto proto, unsigned scancode, unsigned *buf) |
| { |
| sharp_add_bits(&buf, scancode >> 8, 5); |
| sharp_add_bits(&buf, scancode, 8); |
| sharp_add_bits(&buf, 1, 2); |
| |
| *buf++ = NS_TO_US(sharp_unit * 8); |
| *buf++ = NS_TO_US(sharp_unit * 1000); |
| |
| sharp_add_bits(&buf, scancode >> 8, 5); |
| sharp_add_bits(&buf, ~scancode, 8); |
| sharp_add_bits(&buf, ~1, 2); |
| *buf++ = NS_TO_US(sharp_unit * 8); |
| |
| return (13 + 2) * 4 + 3; |
| } |
| |
| static const int sony_unit = 600000; |
| |
| static void sony_add_bits(unsigned *buf, int *n, int bits, int count) |
| { |
| int i; |
| for (i=0; i<count; i++) { |
| if (bits & (1 << i)) |
| buf[(*n)++] = NS_TO_US(sony_unit * 2); |
| else |
| buf[(*n)++] = NS_TO_US(sony_unit); |
| |
| buf[(*n)++] = NS_TO_US(sony_unit); |
| } |
| } |
| |
| static int sony_encode(enum rc_proto proto, unsigned scancode, unsigned *buf) |
| { |
| int n = 0; |
| |
| buf[n++] = NS_TO_US(sony_unit * 4); |
| buf[n++] = NS_TO_US(sony_unit); |
| |
| switch (proto) { |
| case RC_PROTO_SONY12: |
| sony_add_bits(buf, &n, scancode, 7); |
| sony_add_bits(buf, &n, scancode >> 16, 5); |
| break; |
| case RC_PROTO_SONY15: |
| sony_add_bits(buf, &n, scancode, 7); |
| sony_add_bits(buf, &n, scancode >> 16, 8); |
| break; |
| case RC_PROTO_SONY20: |
| sony_add_bits(buf, &n, scancode, 7); |
| sony_add_bits(buf, &n, scancode >> 16, 5); |
| sony_add_bits(buf, &n, scancode >> 8, 8); |
| break; |
| default: |
| return 0; |
| } |
| |
| /* ignore last space */ |
| return n - 1; |
| } |
| |
| static const unsigned int rc5_unit = 888888; |
| |
| static void rc5_advance_space(unsigned *buf, unsigned *n, unsigned length) |
| { |
| if (*n % 2) |
| buf[*n] += length; |
| else |
| buf[++(*n)] = length; |
| } |
| |
| static void rc5_advance_pulse(unsigned *buf, unsigned *n, unsigned length) |
| { |
| if (*n % 2) |
| buf[++(*n)] = length; |
| else |
| buf[*n] += length; |
| } |
| |
| static void rc5_add_bits(unsigned *buf, unsigned *n, int bits, int count) |
| { |
| while (count--) { |
| if (bits & (1 << count)) { |
| rc5_advance_space(buf, n, NS_TO_US(rc5_unit)); |
| rc5_advance_pulse(buf, n, NS_TO_US(rc5_unit)); |
| } else { |
| rc5_advance_pulse(buf, n, NS_TO_US(rc5_unit)); |
| rc5_advance_space(buf, n, NS_TO_US(rc5_unit)); |
| } |
| } |
| } |
| |
| static int rc5_encode(enum rc_proto proto, unsigned scancode, unsigned *buf) |
| { |
| unsigned n = 0; |
| |
| buf[n] = NS_TO_US(rc5_unit); |
| |
| switch (proto) { |
| default: |
| return 0; |
| case RC_PROTO_RC5: |
| rc5_add_bits(buf, &n, !(scancode & 0x40), 1); |
| rc5_add_bits(buf, &n, 0, 1); |
| rc5_add_bits(buf, &n, scancode >> 8, 5); |
| rc5_add_bits(buf, &n, scancode, 6); |
| break; |
| case RC_PROTO_RC5_SZ: |
| rc5_add_bits(buf, &n, !!(scancode & 0x2000), 1); |
| rc5_add_bits(buf, &n, 0, 1); |
| rc5_add_bits(buf, &n, scancode >> 6, 6); |
| rc5_add_bits(buf, &n, scancode, 6); |
| break; |
| case RC_PROTO_RC5X_20: |
| rc5_add_bits(buf, &n, !(scancode & 0x4000), 1); |
| rc5_add_bits(buf, &n, 0, 1); |
| rc5_add_bits(buf, &n, scancode >> 16, 5); |
| rc5_advance_space(buf, &n, NS_TO_US(rc5_unit * 4)); |
| rc5_add_bits(buf, &n, scancode >> 8, 6); |
| rc5_add_bits(buf, &n, scancode, 6); |
| break; |
| } |
| |
| /* drop any trailing pulse */ |
| return (n % 2) ? n : n + 1; |
| } |
| |
| static const unsigned int rc6_unit = 444444; |
| |
| static void rc6_advance_space(unsigned *buf, unsigned *n, unsigned length) |
| { |
| if (*n % 2) |
| buf[*n] += length; |
| else |
| buf[++(*n)] = length; |
| } |
| |
| static void rc6_advance_pulse(unsigned *buf, unsigned *n, unsigned length) |
| { |
| if (*n % 2) |
| buf[++(*n)] = length; |
| else |
| buf[*n] += length; |
| } |
| |
| static void rc6_add_bits(unsigned *buf, unsigned *n, |
| unsigned bits, unsigned count, unsigned length) |
| { |
| while (count--) { |
| if (bits & (1 << count)) { |
| rc6_advance_pulse(buf, n, length); |
| rc6_advance_space(buf, n, length); |
| } else { |
| rc6_advance_space(buf, n, length); |
| rc6_advance_pulse(buf, n, length); |
| } |
| } |
| } |
| |
| static int rc6_encode(enum rc_proto proto, unsigned scancode, unsigned *buf) |
| { |
| unsigned n = 0; |
| buf[n++] = NS_TO_US(rc6_unit * 6); |
| buf[n++] = NS_TO_US(rc6_unit * 2); |
| buf[n] = 0; |
| |
| switch (proto) { |
| default: |
| return 0; |
| case RC_PROTO_RC6_0: |
| rc6_add_bits(buf, &n, 8, 4, NS_TO_US(rc6_unit)); |
| rc6_add_bits(buf, &n, 0, 1, NS_TO_US(rc6_unit * 2)); |
| rc6_add_bits(buf, &n, scancode, 16, NS_TO_US(rc6_unit)); |
| break; |
| case RC_PROTO_RC6_6A_20: |
| rc6_add_bits(buf, &n, 14, 4, NS_TO_US(rc6_unit)); |
| rc6_add_bits(buf, &n, 0, 1, NS_TO_US(rc6_unit * 2)); |
| rc6_add_bits(buf, &n, scancode, 20, NS_TO_US(rc6_unit)); |
| break; |
| case RC_PROTO_RC6_6A_24: |
| rc6_add_bits(buf, &n, 14, 4, NS_TO_US(rc6_unit)); |
| rc6_add_bits(buf, &n, 0, 1, NS_TO_US(rc6_unit * 2)); |
| rc6_add_bits(buf, &n, scancode, 24, NS_TO_US(rc6_unit)); |
| break; |
| case RC_PROTO_RC6_6A_32: |
| case RC_PROTO_RC6_MCE: |
| rc6_add_bits(buf, &n, 14, 4, NS_TO_US(rc6_unit)); |
| rc6_add_bits(buf, &n, 0, 1, NS_TO_US(rc6_unit * 2)); |
| rc6_add_bits(buf, &n, scancode, 32, NS_TO_US(rc6_unit)); |
| break; |
| } |
| |
| /* drop any trailing pulse */ |
| return (n % 2) ? n : n + 1; |
| } |
| |
| static int xbox_dvd_encode(enum rc_proto proto, unsigned scancode, unsigned *buf) |
| { |
| int len = 0; |
| |
| buf[len++] = 4000; |
| buf[len++] = 3900; |
| |
| scancode &= 0xfff; |
| scancode |= (~scancode << 12) & 0xfff000; |
| |
| for (int i=23; i >=0; i--) { |
| buf[len++] = 550; |
| |
| if (scancode & (1 << i)) |
| buf[len++] = 1900; |
| else |
| buf[len++] = 900; |
| } |
| |
| buf[len++]= 550; |
| |
| return len; |
| } |
| |
| static const struct { |
| char name[10]; |
| unsigned scancode_mask; |
| unsigned max_edges; |
| unsigned carrier; |
| int (*encode)(enum rc_proto proto, unsigned scancode, unsigned *buf); |
| } protocols[] = { |
| [RC_PROTO_UNKNOWN] = { "unknown" }, |
| [RC_PROTO_OTHER] = { "other" }, |
| [RC_PROTO_RC5] = { "rc5", 0x1f7f, 24, 36000, rc5_encode }, |
| [RC_PROTO_RC5X_20] = { "rc5x_20", 0x1f7f3f, 40, 36000, rc5_encode }, |
| [RC_PROTO_RC5_SZ] = { "rc5_sz", 0x2fff, 26, 36000, rc5_encode }, |
| [RC_PROTO_SONY12] = { "sony12", 0x1f007f, 25, 40000, sony_encode }, |
| [RC_PROTO_SONY15] = { "sony15", 0xff007f, 31, 40000, sony_encode }, |
| [RC_PROTO_SONY20] = { "sony20", 0x1fff7f, 41, 40000, sony_encode }, |
| [RC_PROTO_JVC] = { "jvc", 0xffff, 35, 38000, jvc_encode }, |
| [RC_PROTO_NEC] = { "nec", 0xffff, 67, 38000, nec_encode }, |
| [RC_PROTO_NECX] = { "necx", 0xffffff, 67, 38000, nec_encode }, |
| [RC_PROTO_NEC32] = { "nec32", 0xffffffff, 67, 38000, nec_encode }, |
| [RC_PROTO_SANYO] = { "sanyo", 0x1fffff, 87, 38000, sanyo_encode }, |
| [RC_PROTO_RC6_0] = { "rc6_0", 0xffff, 24, 36000, rc6_encode }, |
| [RC_PROTO_RC6_6A_20] = { "rc6_6a_20", 0xfffff, 52, 36000, rc6_encode }, |
| [RC_PROTO_RC6_6A_24] = { "rc6_6a_24", 0xffffff, 60, 36000, rc6_encode }, |
| [RC_PROTO_RC6_6A_32] = { "rc6_6a_32", 0xffffffff, 76, 36000, rc6_encode }, |
| [RC_PROTO_RC6_MCE] = { "rc6_mce", 0xffff7fff, 76, 36000, rc6_encode }, |
| [RC_PROTO_SHARP] = { "sharp", 0x1fff, 63, 38000, sharp_encode }, |
| [RC_PROTO_MCIR2_KBD] = { "mcir2-kbd" }, |
| [RC_PROTO_MCIR2_MSE] = { "mcir2-mse" }, |
| [RC_PROTO_XMP] = { "xmp" }, |
| [RC_PROTO_CEC] = { "cec" }, |
| [RC_PROTO_IMON] = { "imon", 0x7fffffff }, |
| [RC_PROTO_RCMM12] = { "rc-mm-12", 0x0fff }, |
| [RC_PROTO_RCMM24] = { "rc-mm-24", 0xffffff }, |
| [RC_PROTO_RCMM32] = { "rc-mm-32", 0xffffffff }, |
| [RC_PROTO_XBOX_DVD] = { "xbox-dvd", 0xfff, 68, 38000, xbox_dvd_encode }, |
| }; |
| |
| static bool str_like(const char *a, const char *b) |
| { |
| while (*a && *b) { |
| while (*a == ' ' || *a == '-' || *a == '_') |
| a++; |
| while (*b == ' ' || *b == '-' || *b == '_') |
| b++; |
| |
| if (*a >= 0x7f || *b >= 0x7f) |
| return false; |
| |
| if (tolower(*a) != tolower(*b)) |
| return false; |
| |
| a++; b++; |
| } |
| |
| return !*a && !*b; |
| } |
| |
| bool protocol_match(const char *name, enum rc_proto *proto) |
| { |
| enum rc_proto p; |
| |
| for (p=0; p<ARRAY_SIZE(protocols); p++) { |
| if (str_like(protocols[p].name, name)) { |
| *proto = p; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| unsigned protocol_carrier(enum rc_proto proto) |
| { |
| return protocols[proto].carrier; |
| } |
| |
| unsigned protocol_max_size(enum rc_proto proto) |
| { |
| return protocols[proto].max_edges; |
| } |
| |
| unsigned protocol_scancode_mask(enum rc_proto proto) |
| { |
| return protocols[proto].scancode_mask; |
| } |
| |
| void protocol_scancode_valid(enum rc_proto *p, unsigned *s) |
| { |
| enum rc_proto p2 = *p; |
| unsigned s2 = *s; |
| |
| // rc6_mce is rc6_6a_32 with vendor code 0x800f and |
| if (*p == RC_PROTO_RC6_MCE && (*s & 0xffff0000) != 0x800f0000) { |
| p2 = RC_PROTO_RC6_6A_32; |
| } else if (*p == RC_PROTO_RC6_6A_32 && (*s & 0xffff0000) == 0x800f0000) { |
| p2 = RC_PROTO_RC6_MCE; |
| } else if (*p == RC_PROTO_NEC || *p == RC_PROTO_NECX || *p == RC_PROTO_NEC32) { |
| // nec scancodes may repeat the address and command |
| // in inverted form; the inverted values are not in the |
| // scancode. |
| |
| // can 24 bit scancode be represented as 16 bit scancode |
| if (*s > 0x0000ffff && *s <= 0x00ffffff) { |
| if ((((*s >> 16) ^ ~(*s >> 8)) & 0xff) != 0) { |
| // is it necx |
| p2 = RC_PROTO_NECX; |
| } else { |
| // or regular nec |
| s2 = ((*s >> 8) & 0xff00) | (*s & 0x00ff); |
| p2 = RC_PROTO_NEC; |
| } |
| // can 32 bit scancode be represented as 24 or 16 bit scancode |
| } else if (*s > 0x00ffffff) { |
| if (((((*s >> 24) ^ ~(*s >> 16)) & 0xff) == 0) && |
| ((((*s >> 8) ^ ~(*s >> 0)) & 0xff) == 0)) { |
| // is it nec |
| s2 = ((*s >> 16) & 0xff00) | |
| ((*s >> 8) & 0x00ff); |
| p2 = RC_PROTO_NEC; |
| } else if (((((*s >> 24) ^ ~(*s >> 16)) & 0xff) != 0) && |
| ((((*s >> 8) ^ ~(*s >> 0)) & 0xff) == 0)) { |
| // is it nec-x |
| s2 = (*s >> 8) & 0xffffff; |
| p2 = RC_PROTO_NECX; |
| } else { |
| // or it has to be nec32 |
| p2 = RC_PROTO_NEC32; |
| } |
| } |
| } |
| |
| s2 &= protocols[p2].scancode_mask; |
| |
| if (*p != p2 || *s != s2) { |
| fprintf(stderr, |
| "warning: `%s:0x%x' will be decoded as `%s:0x%x'\n", |
| protocol_name(*p), *s, protocol_name(p2), s2); |
| |
| *p = p2; |
| *s = s2; |
| } |
| } |
| |
| bool protocol_encoder_available(enum rc_proto proto) |
| { |
| return protocols[proto].encode != NULL; |
| } |
| |
| unsigned protocol_encode(enum rc_proto proto, unsigned scancode, unsigned *buf) |
| { |
| if (!protocols[proto].encode) |
| return 0; |
| |
| return protocols[proto].encode(proto, scancode, buf); |
| } |
| |
| const char* protocol_name(enum rc_proto proto) |
| { |
| if (proto >= ARRAY_SIZE(protocols) || !protocols[proto].name[0]) |
| return NULL; |
| |
| return protocols[proto].name; |
| } |