[sane-devel] AXIS 1650 support for Pixma backend - work in progress

Ondrej Zary linux at zary.sk
Fri Mar 20 17:44:49 GMT 2020


Hello,
I have a Canon MF5730 MFP with external AXIS 1650 print server:
https://www.axis.com/techsup/prtsrv/axis_1650/index.htm

I've reverse engineered the print data format and wrote CUPS driver couple of
years ago so printing works fine.

Now it's time for scanning. Thanks to SANE and Pixma backend, scanning works
when connected directly using the USB port (except for the buttons).

The scan mode of AXIS 1650 works as USB-over-TCP.
The Windows AXIS Scan Client discovers and identifies local scan-capable print
servers using a simple UDP-based WIMP protocol:
https://www.axis.com/techsup/prtsrv/axis_1650/technotes_1650.htm#2

Then it connects to the selected print server using TCP, locking it for the
current user, and creates a virtual USB device so the Canon driver could be
used to scan.

I've based AXIS support for Pixma backend on the BJNP code.
What works:
 - autodetection
 - connect
 - scan starts
What does not:
 - data gets messed up somehow
 - scan does not finish successfully
 - set_timeout, interrupts not implemented


diff --git a/backend/Makefile.am b/backend/Makefile.am
index a28e749f..274d5490 100644
--- a/backend/Makefile.am
+++ b/backend/Makefile.am
@@ -821,7 +821,7 @@ libsane_pint_la_CPPFLAGS = $(AM_CPPFLAGS) -DBACKEND_NAME=pint
 libsane_pint_la_LDFLAGS = $(DIST_SANELIBS_LDFLAGS)
 libsane_pint_la_LIBADD = $(COMMON_LIBS) libpint.la ../sanei/sanei_init_debug.lo ../sanei/sanei_constrain_value.lo ../sanei/sanei_config.lo  sane_strstatus.lo
 
-libpixma_la_SOURCES = pixma.c pixma.h pixma_io_sanei.c pixma_io.h pixma_common.c pixma_common.h pixma_mp150.c pixma_mp730.c pixma_mp750.c pixma_mp810.c pixma_imageclass.c pixma_bjnp.c pixma_bjnp.h pixma_bjnp_private.h pixma_rename.h
+libpixma_la_SOURCES = pixma.c pixma.h pixma_io_sanei.c pixma_io.h pixma_common.c pixma_common.h pixma_mp150.c pixma_mp730.c pixma_mp750.c pixma_mp810.c pixma_imageclass.c pixma_bjnp.c pixma_bjnp.h pixma_bjnp_private.h pixma_rename.h pixma_axis.c pixma_axis.h
 libpixma_la_CPPFLAGS = $(AM_CPPFLAGS) -DBACKEND_NAME=pixma
 
 nodist_libsane_pixma_la_SOURCES = pixma-s.c
diff --git a/backend/pixma_axis.c b/backend/pixma_axis.c
new file mode 100644
index 00000000..0c2b528e
--- /dev/null
+++ b/backend/pixma_axis.c
@@ -0,0 +1,576 @@
+#undef BACKEND_NAME
+#define BACKEND_NAME axis
+
+#include  "../include/sane/config.h"
+#include  "../include/sane/sane.h"
+
+/*
+ * Standard types etc
+ */
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#include <unistd.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+
+/*
+ * networking stuff
+ */
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <net/if.h>
+#ifdef HAVE_IFADDRS_H
+#include <ifaddrs.h>
+#endif
+#ifdef HAVE_SYS_SELSECT_H
+#include <sys/select.h>
+#endif
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+#include <errno.h>
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#include "pixma_axis_private.h"
+#include "pixma_axis.h"
+#include "pixma.h"
+#include "pixma_common.h"
+
+#define MAX_PACKET_DATA_SIZE	65535
+#define RECEIVE_TIMEOUT		2
+
+/* static data */
+static axis_device_t device[AXIS_NO_DEVICES];
+static int axis_no_devices = 0;
+
+extern void
+sanei_axis_init (void)
+{
+  DBG_INIT();
+  axis_no_devices = 0;
+}
+
+static char *getusername(void) {
+  static char noname[] = "sane_pixma";
+  struct passwd *pwdent;
+
+#ifdef HAVE_PWD_H
+  if (((pwdent = getpwuid(geteuid())) != NULL) && (pwdent->pw_name != NULL))
+    return pwdent->pw_name;
+#endif
+  return noname;
+}
+
+static ssize_t receive_packet(int socket, void *packet, size_t len, struct sockaddr_in *from) {
+  fd_set rfds;
+  struct timeval tv;
+  ssize_t received;
+  socklen_t from_len = sizeof(struct sockaddr_in);
+
+  tv.tv_sec = RECEIVE_TIMEOUT;
+  tv.tv_usec = 0;
+  /* Watch socket to see when it has input. */
+  FD_ZERO(&rfds);
+  FD_SET(socket, &rfds);
+
+  switch (select(socket + 1, &rfds, NULL, NULL, &tv)) {
+  case 0:
+    return 0;
+  case -1:
+    DBG(LOG_CRIT, "select() failed");
+    return 0;
+  default:
+    received = recvfrom(socket, packet, len, 0, (struct sockaddr *)from, &from_len);
+    if (received < 0) {
+      DBG(LOG_CRIT, "Error receiving packet");
+      exit(2);
+    }
+/*#ifdef DEBUG
+    int i;
+    for (i = 0; i < received; i++)
+      fprintf(stderr, "%.2hhX ",((char *)packet)[i]);
+    fprintf(stderr, "\n");
+#endif*/
+    return received;
+  }
+}
+
+static ssize_t axis_send_wimp(int udp_socket, uint8_t cmd, void *data, uint16_t len, struct sockaddr *addr, socklen_t addrlen) {
+  uint8_t packet[MAX_PACKET_DATA_SIZE];
+  struct axis_wimp_header *header = (void *)packet;
+  ssize_t ret;
+
+  header->type = cmd;
+  header->magic = 0x03;
+  header->zero = 0x00;
+  memcpy(packet + sizeof(struct axis_wimp_header), data, len);
+  ret = sendto(udp_socket, packet, sizeof(struct axis_wimp_header) + len, 0, addr, addrlen);
+  if (ret != (int)sizeof(struct axis_wimp_header) + len) {
+    DBG(LOG_CRIT, "Unable to send UDP packet");
+    return ret;
+  }
+
+  return 0;
+}
+
+static ssize_t axis_wimp_get(int udp_socket, uint16_t remote_port, uint8_t cmd, uint8_t idx, char *data_out, uint16_t len_out) {
+  ssize_t ret;
+  uint16_t len;
+  struct axis_wimp_get wimp_get;
+  struct axis_wimp_header *reply = (void *)data_out;
+  struct axis_wimp_get_reply *str = (void *)(data_out + sizeof(struct axis_wimp_header));
+
+  wimp_get.port = cpu_to_le16(remote_port),
+  wimp_get.magic = 0x02,
+  wimp_get.zero = 0;
+  wimp_get.cmd = cmd,
+  wimp_get.idx = idx,
+  ret = axis_send_wimp(udp_socket, WIMP_SERVER_STATUS, &wimp_get, sizeof(wimp_get), NULL, 0);
+  if (ret)
+    return ret;
+
+  ret = receive_packet(udp_socket, data_out, len_out, NULL);
+  if (ret < (int)sizeof(struct axis_wimp_header)) {
+    DBG(LOG_NOTICE, "Received packet is too short\n");
+    return -1;
+  }
+  if (reply->type != (WIMP_SERVER_STATUS | WIMP_REPLY)) {
+    DBG(LOG_NOTICE, "Received invalid reply\n");
+    return -1;
+  }
+  len = le16_to_cpu(str->len) - 2;
+  memmove(data_out, data_out + sizeof(struct axis_wimp_header) + sizeof(struct axis_wimp_get_reply), len);
+  data_out[len] = '\0';
+
+  return 0;
+}
+
+static int create_udp_socket(uint32_t addr, uint16_t *source_port) {
+  int udp_socket;
+  int enable = 1;
+  struct sockaddr_in address;
+  socklen_t sock_len;
+
+  udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
+  if (udp_socket < 0) {
+    DBG(LOG_CRIT, "Unable to create UDP socket");
+    return -1;
+  }
+
+  if (setsockopt(udp_socket, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(enable))) {
+    DBG(LOG_CRIT, "Unable to enable broadcast");
+    return -1;
+  }
+
+  address.sin_family = AF_INET;
+  address.sin_port = 0; /* random */
+  address.sin_addr.s_addr = addr;
+  if (bind(udp_socket, (struct sockaddr *)&address, sizeof(address)) < 0) {
+    DBG(LOG_CRIT, "Unable to bind UDP socket");
+    return -1;
+  }
+
+  /* get assigned source port */
+  sock_len = sizeof(address);
+  getsockname(udp_socket, (struct sockaddr *)&address, &sock_len);
+  *source_port = ntohs(address.sin_port);
+
+  return udp_socket;
+}
+
+static int get_server_status(int udp_socket, uint32_t addr, uint16_t remote_port) {
+  char buf[MAX_PACKET_DATA_SIZE];
+  struct sockaddr_in address;
+
+  address.sin_family = AF_INET;
+  address.sin_port = htons(AXIS_WIMP_PORT);
+  address.sin_addr.s_addr = addr;
+
+  /* connect the socket to this print server only */
+
+  if (connect(udp_socket, (struct sockaddr *)&address, sizeof(address)) < 0) {
+    DBG(LOG_CRIT, "Unable to connect UDP socket");
+    return -1;
+  }
+
+  /* get device status (IDLE/BUSY) */
+  if (axis_wimp_get(udp_socket, remote_port, WIMP_GET_STATUS, 1, buf, sizeof(buf)))
+    DBG(LOG_NOTICE, "Error getting device status\n");
+  DBG(LOG_INFO, "device status=%s\n", buf);
+
+  /* get username if BUSY */
+  if (!strcmp((char *)buf, "BUSY_TXT")) {
+    if (axis_wimp_get(udp_socket, remote_port, WIMP_GET_STATUS, 2, buf, sizeof(buf)))
+      DBG(LOG_NOTICE, "Error getting user name\n");
+    DBG(LOG_INFO, "username=%s\n", buf);
+    return 1;
+  }
+
+  return 0;
+}
+
+static int get_device_name(int udp_socket, uint32_t addr, uint16_t remote_port, char *devname, int devname_len) {
+  char buf[MAX_PACKET_DATA_SIZE];
+  struct sockaddr_in address;
+
+  address.sin_family = AF_INET;
+  address.sin_port = htons(AXIS_WIMP_PORT);
+  address.sin_addr.s_addr = addr;
+
+  /* connect the socket to this print server only */
+
+  if (connect(udp_socket, (struct sockaddr *)&address, sizeof(address)) < 0) {
+    DBG(LOG_CRIT, "Unable to connect UDP socket");
+    return -1;
+  }
+
+  /* get device name */
+  if (axis_wimp_get(udp_socket, remote_port, WIMP_GET_NAME, 1, buf, sizeof(buf)))
+    DBG(LOG_NOTICE, "Error getting device name\n");
+  DBG(LOG_INFO, "name=%s\n", buf);
+
+  strncpy(devname, buf, devname_len);
+
+  return 0;
+}
+
+static int send_discover(int udp_socket, uint32_t addr, uint16_t source_port) {
+  int ret;
+  struct sockaddr_in address;
+  uint8_t get_info[2];
+
+  address.sin_family = AF_INET;
+  address.sin_port = htons(AXIS_WIMP_PORT);
+  address.sin_addr.s_addr = addr;
+
+  get_info[0] = source_port & 0xff;
+  get_info[1] = source_port >> 8;
+
+  ret = axis_send_wimp(udp_socket, WIMP_SERVER_INFO, get_info, sizeof(get_info), (struct sockaddr *)&address, sizeof(address));
+  if (ret)
+    DBG(LOG_CRIT, "Unable to send discover packet");
+
+  return ret;
+}
+
+static int send_broadcasts(int udp_socket, uint16_t source_port) {
+  struct ifaddrs *ifaddr, *ifa;
+  int num_sent = 0;
+
+  if (getifaddrs(&ifaddr) == -1) {
+    DBG(LOG_CRIT, "Unable to obtain network interface list");
+    return -1;
+  }
+
+  /* Walk through all interfaces */
+  for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+    /* we're interested only in broadcast-capable IPv4 interfaces */
+    if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET && ifa->ifa_flags & IFF_BROADCAST) {
+      struct sockaddr_in *bcast = (struct sockaddr_in *)ifa->ifa_ifu.ifu_broadaddr;
+      DBG(LOG_INFO, "%s: %s\n", ifa->ifa_name, inet_ntoa(bcast->sin_addr));
+      if (send_discover(udp_socket, bcast->sin_addr.s_addr, source_port) == 0)
+        num_sent++;
+    }
+  }
+
+  freeifaddrs(ifaddr);
+
+  return num_sent;
+}
+
+int axis_send_cmd(int tcp_socket, uint8_t cmd, void *data, uint16_t len) {
+  uint8_t packet[MAX_PACKET_DATA_SIZE];
+  struct axis_header *header = (void *)packet;
+  int ret;
+  DBG(LOG_INFO, "%s(0x%02x, %d)\n", __func__, cmd, len);
+
+  header->type = AXIS_HDR_REQUEST;
+  header->len = cpu_to_le16(len + sizeof(struct axis_cmd));
+  ret = send(tcp_socket, packet, sizeof(struct axis_header), 0);
+  for (int i = 0; i < ret; i++)
+    fprintf(stderr, "%02x ", packet[i]);
+  fprintf(stderr, "\n");
+  if (ret < 0) {
+    perror("Error sending packet");
+    return ret;
+  }
+
+  struct axis_cmd *command = (void *)packet;
+  command->cmd = cmd;
+  command->len = cpu_to_le16(len);
+  memcpy(packet + sizeof(struct axis_cmd), data, len);
+  ret = send(tcp_socket, packet, sizeof(struct axis_cmd) + len, 0);
+  for (int i = 0; i < ret; i++)
+    fprintf(stderr, "%02x ", packet[i]);
+  fprintf(stderr, "\n");
+  if (ret < 0) {
+    perror("Error sending packet");
+    return ret;
+  }
+
+  return 0;
+}
+
+int axis_recv(SANE_Int dn, void *data, size_t *len) {
+  uint8_t packet[MAX_PACKET_DATA_SIZE];
+  struct axis_header *header = (void *)packet;
+  struct axis_reply *reply = (void *)packet;
+  ssize_t ret;
+  int i;
+
+retry:
+
+  ret = recv(device[dn].tcp_socket, packet, sizeof(struct axis_header), 0);
+  fprintf(stderr, "got1: ");
+  for (i = 0; i < ret; i++) {
+    fprintf(stderr, "%02x ", packet[i]);
+  }
+  fprintf(stderr, "\n");
+
+  if (header->type != AXIS_HDR_REPLY) {
+    fprintf(stderr, "not reply!\n");
+    return -1;
+  }
+  *len = le16_to_cpu(header->len);
+  fprintf(stderr, "len=0x%x\n", *len);
+  ret = recv(device[dn].tcp_socket, packet, *len, 0);
+  fprintf(stderr, "got2: ");
+  for (i = 0; i < ret; i++) {
+    fprintf(stderr, "%02x ", packet[i]);
+  }
+  fprintf(stderr, "\n");
+  *len = le16_to_cpu(reply->len);
+  if (reply->cmd == AXIS_CMD_UNKNOWN2) { /// interrupt???
+    fprintf(stderr, "interrupt?????\n");
+    memcpy(device[dn].int_data, packet + sizeof(struct axis_reply), *len);
+    device[dn].int_size = *len;
+    goto retry;
+  }
+  memcpy(data, packet + sizeof(struct axis_reply), *len);
+  if (reply->status != 0) {
+    fprintf(stderr, "status=0x%x\n", le16_to_cpu(reply->status));
+    return SANE_STATUS_IO_ERROR;
+  }
+
+  return 0;
+}
+
+
+/**
+ * Find AXIS printservers with Canon support
+ *
+ * The function attach is called for every device which has been found.
+ *
+ * @param attach attach function
+ *
+ * @return SANE_STATUS_GOOD - on success (even if no scanner was found)
+ */
+extern SANE_Status
+sanei_axis_find_devices (const char **conf_devices,
+			 SANE_Status (*attach_axis)
+			 (SANE_String_Const devname,
+			  SANE_String_Const makemodel,
+			  SANE_String_Const serial,
+			  const struct pixma_config_t *
+			  const pixma_devices[]),
+			 const struct pixma_config_t *const pixma_devices[])
+{
+  char devname[256];
+  char uri[256];
+  uint8_t packet[MAX_PACKET_DATA_SIZE];
+  struct sockaddr_in from;
+  uint16_t source_port, remote_port;
+  int udp_socket, num_ifaces;
+
+  udp_socket = create_udp_socket(htonl(INADDR_ANY), &source_port);
+  if (udp_socket < 0)
+    return SANE_STATUS_IO_ERROR;
+  DBG(LOG_INFO, "source port=%d\n", source_port);
+
+  /* send broadcast discover packets to all interfaces */
+  num_ifaces = send_broadcasts(udp_socket, source_port);
+  DBG(LOG_INFO, "sent broadcasts to %d interfaces\n", num_ifaces);
+
+  /* wait for response packets */
+  while (receive_packet(udp_socket, packet, sizeof(packet), &from) != 0) {
+    struct axis_wimp_header *header = (void *)packet;
+//    struct axis_wimp_server_info *s_info = (void *)(packet + sizeof(struct axis_wimp_header));
+
+    DBG(LOG_INFO, "got reply from %s\n", inet_ntoa(from.sin_addr));
+    /* get remote port */
+    remote_port = ntohs(from.sin_port);
+    DBG(LOG_INFO, "remote port=%d\n", remote_port);
+    if (header->type != (WIMP_SERVER_INFO | WIMP_REPLY)) {
+      DBG(LOG_NOTICE, "Received invalid reply\n");
+      continue;
+    }
+
+    get_device_name(udp_socket, from.sin_addr.s_addr, remote_port, devname, sizeof(devname));
+    /* construct URI */
+    sprintf (uri, "%s://%s:%d", "axis", inet_ntoa(from.sin_addr), AXIS_SCAN_PORT);
+
+    device[axis_no_devices++].addr = from.sin_addr;
+    attach_axis(uri, devname, inet_ntoa(from.sin_addr), pixma_devices);
+  }
+
+  return SANE_STATUS_GOOD;
+}
+
+extern SANE_Status
+sanei_axis_open (SANE_String_Const devname, SANE_Int * dn)
+{
+  const char *uri_ip, *uri_port;
+  char ip[16];
+  size_t ip_len;
+  int port = AXIS_SCAN_PORT;
+  struct in_addr addr;
+  int i;
+  char *username;
+  struct sockaddr_in address;
+
+  DBG(LOG_INFO, "%s(%s, %d)\n", __func__, devname, *dn);
+  if (strncmp(devname, "axis://", 7)) {
+    DBG(LOG_CRIT, "Invalid protocol in devname");
+    return SANE_STATUS_INVAL;
+  }
+  uri_ip = devname + 7;
+
+  uri_port = strchr(uri_ip, ':');
+  if (uri_port) {
+    sscanf(uri_port, ":%d", &port);
+    ip_len = uri_port - uri_ip;
+  } else
+    ip_len = strlen(uri_ip);
+  if (ip_len > sizeof(ip))
+    ip_len = sizeof(ip);
+  strncpy(ip, uri_ip, ip_len);
+  ip[ip_len] = '\0';
+
+  if (inet_aton(ip, &addr) == 0) {
+    DBG(LOG_CRIT, "Invalid IP address in devname");
+    return SANE_STATUS_INVAL;
+  }
+  DBG(LOG_INFO, "ip=%s, port=%d\n", inet_ntoa(addr), port);
+
+  for (i = 0; i < axis_no_devices; i++)
+    if (device[i].addr.s_addr == addr.s_addr) {
+      DBG(LOG_INFO, "found device at position %d\n", i);
+      *dn = i;
+      /* connect */
+      int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
+      if (tcp_socket < 0) {
+        perror("Unable to create TCP socket");
+        return SANE_STATUS_IO_ERROR;
+      }
+      address.sin_family = AF_INET;
+      /* set TCP destination port and address */
+      address.sin_port = htons(AXIS_SCAN_PORT);
+      address.sin_addr.s_addr = addr.s_addr;
+      if (connect(tcp_socket, (struct sockaddr *) &address, sizeof(address)) < 0) {
+        perror("Unable to connect");
+        return SANE_STATUS_IO_ERROR;
+      }
+      DBG(LOG_INFO, "connected\n");
+
+      device[i].tcp_socket = tcp_socket;
+
+      username = getusername();
+      axis_send_cmd(tcp_socket, AXIS_CMD_CONNECT, username, strlen(username) + 1);
+      const SANE_Byte *dummy_buf[MAX_PACKET_DATA_SIZE];
+      size_t dummy_len;
+      axis_recv(i, dummy_buf, &dummy_len);
+
+      uint8_t timeout[] = { 0x0e, 0x01, 0x00, 0x00 };
+      axis_send_cmd(tcp_socket, AXIS_CMD_UNKNOWN3, timeout, sizeof(timeout));
+      axis_recv(i, dummy_buf, &dummy_len);
+
+      axis_send_cmd(tcp_socket, AXIS_CMD_UNKNOWN, NULL, 0);
+      axis_recv(i, dummy_buf, &dummy_len);
+
+      return SANE_STATUS_GOOD;
+    }
+/*FIXME: add to table */
+  return SANE_STATUS_INVAL;
+}
+
+void
+sanei_axis_close (SANE_Int dn)
+{
+  DBG(LOG_INFO, "%s(%d)\n", __func__, dn);
+}
+
+extern void
+sanei_axis_set_timeout (SANE_Int dn, SANE_Int timeout)
+{
+  DBG(LOG_INFO, "%s(%d, %d)\n", __func__, dn, timeout);
+  device[dn].axis_timeout = timeout;
+}
+
+extern SANE_Status
+sanei_axis_read_bulk (SANE_Int dn, SANE_Byte * buffer, size_t * size)
+{
+  int i;
+  DBG(LOG_INFO, "%s(%d, %p, %d)\n", __func__, dn, buffer, *size);
+//  uint8_t buf_read[] = { 0x40, 0x00 };
+  uint16_t read_size = cpu_to_le16(*size);
+//  axis_send_cmd(device[dn].tcp_socket, AXIS_CMD_READ, buf_read, sizeof(buf_read));
+  axis_send_cmd(device[dn].tcp_socket, AXIS_CMD_READ, &read_size, sizeof(read_size));
+  axis_recv(dn, buffer, size);  ////FIXME
+  fprintf(stderr, "sanei_axis_read_bulk: ");
+  for (i = 0; i < *size; i++) {
+    fprintf(stderr, "%02x ", buffer[i]);
+  }
+  fprintf(stderr, "\n");
+  return SANE_STATUS_GOOD;
+}
+
+extern SANE_Status
+sanei_axis_write_bulk (SANE_Int dn, const SANE_Byte * buffer, size_t * size)
+{
+  const SANE_Byte *dummy_buf[MAX_PACKET_DATA_SIZE];
+  size_t dummy_len;
+  int i;
+  DBG(LOG_INFO, "%s(%d, %p, %d)\n", __func__, dn, buffer, *size);
+  fprintf(stderr, "sanei_axis_write_bulk: ");
+  for (i = 0; i < *size; i++) {
+    fprintf(stderr, "%02x ", buffer[i]);
+  }
+  fprintf(stderr, "\n");
+  axis_send_cmd(device[dn].tcp_socket, AXIS_CMD_WRITE, buffer, *size);
+  axis_recv(dn, dummy_buf, &dummy_len);	////FIXME
+  return SANE_STATUS_GOOD;
+}
+
+extern SANE_Status
+sanei_axis_read_int (SANE_Int dn, SANE_Byte * buffer, size_t * size)
+{
+  DBG(LOG_INFO, "%s(%d, %p, %d)\n", __func__, dn, buffer, *size);
+  if (!device[dn].int_size)
+    return SANE_STATUS_EOF;
+  memcpy(buffer, device[dn].int_data, device[dn].int_size);
+  *size = device[dn].int_size;
+  device[dn].int_size = 0;
+  return SANE_STATUS_GOOD;
+//  return SANE_STATUS_EOF;
+}
diff --git a/backend/pixma_axis.h b/backend/pixma_axis.h
new file mode 100644
index 00000000..224f3321
--- /dev/null
+++ b/backend/pixma_axis.h
@@ -0,0 +1,154 @@
+#ifndef sanei_axis_h
+#define sanei_axis_h
+
+#include "../include/sane/config.h"
+#include "../include/sane/sane.h"
+#include "pixma.h"
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>		/* for size_t */
+#endif
+
+/** Initialize sanei_axis.
+ *
+ * Call this before any other sanei_axis function.
+ */
+extern void sanei_axis_init (void);
+
+/** Find scanners responding to a AXIS broadcast.
+ *
+ * The function attach is called for every device which has been found.
+ * Serial is the address of the scanner in human readable form of max
+ * SHORT_HOSTNAME_MAX characters
+ * @param conf_devices list of pre-configures device URI's to attach
+ * @param attach attach function
+ * @param pixma_devices device informatio needed by attach function
+ *
+ * @return SANE_STATUS_GOOD - on success (even if no scanner was found)
+ */
+
+#define SHORT_HOSTNAME_MAX 16
+
+extern SANE_Status
+sanei_axis_find_devices (const char **conf_devices,
+                         SANE_Status (*attach_axis)
+			 (SANE_String_Const devname,
+			  SANE_String_Const makemodel,
+			  SANE_String_Const serial,
+			  const struct pixma_config_t *
+			  const pixma_devices[]),
+			 const struct pixma_config_t *const pixma_devices[]);
+
+/** Open a AXIS device.
+ *
+ * The device is opened by its name devname and the device number is
+ * returned in dn on success.
+ *
+ * Device names consist of an URI
+ * Where:
+ * method = axis
+ * hostname = resolvable name or IP-address
+ * port = 8612 for a scanner
+ * An example could look like this: axis://host.domain:8612
+ *
+ * @param devname name of the device to open
+ * @param dn device number
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on success
+ * - SANE_STATUS_ACCESS_DENIED - if the file couldn't be accessed due to
+ *   permissions
+ * - SANE_STATUS_INVAL - on every other error
+ */
+extern SANE_Status sanei_axis_open (SANE_String_Const devname, SANE_Int * dn);
+
+/** Close a AXIS device.
+ *
+ * @param dn device number
+ */
+
+extern void sanei_axis_close (SANE_Int dn);
+
+/** Activate a AXIS device connection
+ *
+ *  @param dn device number
+ */
+
+extern SANE_Status sanei_axis_activate (SANE_Int dn);
+
+/** De-activate a AXIS device connection
+ *
+ * @param dn device number
+ */
+
+extern SANE_Status sanei_axis_deactivate (SANE_Int dn);
+
+/** Set the libaxis timeout for bulk and interrupt reads.
+ *
+ * @param devno device number
+ * @param timeout the new timeout in ms
+ */
+extern void sanei_axis_set_timeout (SANE_Int devno, SANE_Int timeout);
+
+/** Check if sanei_axis_set_timeout() is available.
+ */
+#define HAVE_SANEI_AXIS_SET_TIMEOUT
+
+/** Initiate a bulk transfer read.
+ *
+ * Read up to size bytes from the device to buffer. After the read, size
+ * contains the number of bytes actually read.
+ *
+ * @param dn device number
+ * @param buffer buffer to store read data in
+ * @param size size of the data
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_EOF - if zero bytes have been read
+ * - SANE_STATUS_IO_ERROR - if an error occured during the read
+ * - SANE_STATUS_INVAL - on every other error
+ *
+ */
+extern SANE_Status
+sanei_axis_read_bulk (SANE_Int dn, SANE_Byte * buffer, size_t * size);
+
+/** Initiate a bulk transfer write.
+ *
+ * Write up to size bytes from buffer to the device. After the write size
+ * contains the number of bytes actually written.
+ *
+ * @param dn device number
+ * @param buffer buffer to write to device
+ * @param size size of the data
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_IO_ERROR - if an error occured during the write
+ * - SANE_STATUS_INVAL - on every other error
+ */
+extern SANE_Status
+sanei_axis_write_bulk (SANE_Int dn, const SANE_Byte * buffer, size_t * size);
+
+/** Initiate a interrupt transfer read.
+ *
+ * Read up to size bytes from the interrupt endpoint from the device to
+ * buffer. After the read, size contains the number of bytes actually read.
+ *
+ * @param dn device number
+ * @param buffer buffer to store read data in
+ * @param size size of the data
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_EOF - if zero bytes have been read
+ * - SANE_STATUS_IO_ERROR - if an error occured during the read
+ * - SANE_STATUS_INVAL - on every other error
+ *
+ */
+
+extern SANE_Status
+sanei_axis_read_int (SANE_Int dn, SANE_Byte * buffer, size_t * size);
+
+/*------------------------------------------------------*/
+#endif /* sanei_axis_h */
diff --git a/backend/pixma_axis_private.h b/backend/pixma_axis_private.h
new file mode 100644
index 00000000..96f18ca9
--- /dev/null
+++ b/backend/pixma_axis_private.h
@@ -0,0 +1,89 @@
+#include "../include/sane/sanei_debug.h"
+#define LOG_CRIT 0
+#define LOG_NOTICE 1
+#define LOG_INFO 2
+#define LOG_DEBUG 3
+#define LOG_DEBUG2 4
+#define LOG_DEBUG3 5
+
+#define AXIS_NO_DEVICES 16
+
+#define AXIS_WIMP_PORT 		10260	/* UDP port for discovery */
+
+#define cpu_to_le16(x)		(x)
+#define le16_to_cpu(x)		(x)
+
+#define WIMP_SERVER_INFO	0x24
+#define WIMP_SERVER_STATUS	0x30
+#define WIMP_REPLY		(1 << 0)
+struct axis_wimp_header {
+	uint8_t type;			/* 0x24, 0x30 = request, 0x25, 0x31 = reply */
+	uint8_t magic;			/* 0x03 */
+	uint8_t zero;
+} __attribute__((__packed__));
+
+#define WIMP_GET_NAME		0x02
+#define WIMP_GET_STATUS		0x03
+struct axis_wimp_get {
+	uint16_t port;
+	uint8_t magic;			/* 0x02 */
+	uint8_t zero;
+	uint8_t cmd;			/* 0x02 = device name, 0x03 = device status */
+	uint8_t idx;			/* 0x01 = first, 0x02 = second */
+} __attribute__((__packed__));
+
+#define ETHER_ADDR_LEN 6
+struct axis_wimp_server_info {
+	uint8_t mac[ETHER_ADDR_LEN];	/* print server MAC address */
+	char name[16];			/* print server name (max. 15 chars), zero-terminated */
+	uint8_t unknown2[67];
+} __attribute__((__packed__));
+
+struct axis_wimp_get_reply {
+	uint16_t len;
+	uint16_t unknown;
+} __attribute__((__packed__));
+
+#define AXIS_SCAN_PORT		49152	/* TCP port for scan data */
+
+#define AXIS_HDR_REQUEST	0x27
+#define AXIS_HDR_REPLY		0x28
+
+struct axis_header {
+	uint8_t type;
+	uint32_t len;	/* little endian */
+} __attribute__((__packed__));
+
+#define AXIS_CMD_READ		0x01
+#define AXIS_CMD_WRITE		0x02
+#define AXIS_CMD_UNKNOWN	0x03	/* ??? */
+#define AXIS_CMD_UNKNOWN2	0x04	/* interrupt ??? only seen as reply */
+#define AXIS_CMD_CONNECT	0x10
+#define AXIS_CMD_DISCONNECT	0x11
+#define AXIS_CMD_UNKNOWN3	0x12	/* ??? */
+
+struct axis_cmd {
+	uint8_t cmd;
+	uint32_t len;	/* little endian */
+} __attribute__((__packed__));
+
+struct axis_reply {
+	uint8_t cmd;
+	uint32_t status;/* little endian */
+	uint32_t len;	/* little endian */
+}__attribute__((__packed__));
+
+/*
+ * Device information for opened devices
+ */
+
+typedef struct device_s
+{
+  int open;			/* connection to scanner is opened */
+  int tcp_socket;		/* open tcp socket for communcation to scannner */
+  /* device information */
+  struct in_addr addr;		/* IP address of the scanner */
+  int axis_timeout;             /* timeout (msec) for next poll command */
+  int int_size;			/* size of interrupt data */
+  uint8_t int_data[16];		/* interrupt data */
+} axis_device_t;
diff --git a/backend/pixma_io_sanei.c b/backend/pixma_io_sanei.c
index 963b8ea9..378753ca 100644
--- a/backend/pixma_io_sanei.c
+++ b/backend/pixma_io_sanei.c
@@ -53,6 +53,7 @@
 #include "pixma_common.h"
 #include "pixma_io.h"
 #include "pixma_bjnp.h"
+#include "pixma_axis.h"
 
 #include "../include/sane/sanei_usb.h"
 
@@ -90,6 +91,7 @@ typedef struct scanner_info_t
 
 #define INT_USB 0
 #define INT_BJNP 1
+#define INT_AXIS 2
 
 static scanner_info_t *first_scanner = NULL;
 static pixma_io_t *first_io = NULL;
@@ -159,9 +161,9 @@ attach (SANE_String_Const devname)
 
 
 static SANE_Status
-attach_bjnp (SANE_String_Const devname,  SANE_String_Const makemodel,
+attach_net (SANE_String_Const devname,  SANE_String_Const makemodel,
              SANE_String_Const serial,
-             const struct pixma_config_t *const pixma_devices[])
+             const struct pixma_config_t *const pixma_devices[], int interface)
 {
   scanner_info_t *si;
   const pixma_config_t *cfg;
@@ -179,7 +181,7 @@ attach_bjnp (SANE_String_Const devname,  SANE_String_Const makemodel,
     {
       si->cfg = cfg;
       sprintf(si->serial, "%s_%s", cfg->model, serial);
-      si -> interface = INT_BJNP;
+      si -> interface = interface;
       si->next = first_scanner;
       first_scanner = si;
       nscanners++;
@@ -188,6 +190,22 @@ attach_bjnp (SANE_String_Const devname,  SANE_String_Const makemodel,
   return error;
 }
 
+static SANE_Status
+attach_bjnp (SANE_String_Const devname,  SANE_String_Const makemodel,
+             SANE_String_Const serial,
+             const struct pixma_config_t *const pixma_devices[])
+{
+  return attach_net(devname, makemodel, serial, pixma_devices, INT_BJNP);
+}
+
+static SANE_Status
+attach_axis (SANE_String_Const devname,  SANE_String_Const makemodel,
+             SANE_String_Const serial,
+             const struct pixma_config_t *const pixma_devices[])
+{
+  return attach_net(devname, makemodel, serial, pixma_devices, INT_AXIS);
+}
+
 static void
 clear_scanner_list (void)
 {
@@ -335,6 +353,7 @@ pixma_io_init (void)
 {
   sanei_usb_init ();
   sanei_bjnp_init();
+  sanei_axis_init();
   nscanners = 0;
   return 0;
 }
@@ -384,6 +403,16 @@ pixma_collect_devices (const char **conf_devices,
       j++;
 
     }
+  sanei_axis_find_devices(conf_devices, attach_axis, pixma_devices);
+  si = first_scanner;
+  while (j < nscanners)
+    {
+      PDBG (pixma_dbg (3, "pixma_collect_devices() found %s at %s\n",
+               si->cfg->name, si->devname));
+      si = si->next;
+      j++;
+
+    }
   return nscanners;
 }
 
@@ -415,6 +444,8 @@ pixma_connect (unsigned devnr, pixma_io_t ** handle)
     return PIXMA_EINVAL;
   if (si-> interface == INT_BJNP)
     error = map_error (sanei_bjnp_open (si->devname, &dev));
+  else if (si-> interface == INT_AXIS)
+    error = map_error (sanei_axis_open (si->devname, &dev));
   else
     error = map_error (sanei_usb_open (si->devname, &dev));
 
@@ -425,6 +456,8 @@ pixma_connect (unsigned devnr, pixma_io_t ** handle)
     {
       if (si -> interface == INT_BJNP)
         sanei_bjnp_close (dev);
+      else if (si -> interface == INT_AXIS)
+        sanei_axis_close (dev);
       else
         sanei_usb_close (dev);
       return PIXMA_ENOMEM;
@@ -453,6 +486,8 @@ pixma_disconnect (pixma_io_t * io)
     return;
   if (io-> interface == INT_BJNP)
     sanei_bjnp_close (io->dev);
+  else if (io-> interface == INT_AXIS)
+    sanei_axis_close (io->dev);
   else
     sanei_usb_close (io->dev);
   *p = io->next;
@@ -504,6 +539,11 @@ pixma_write (pixma_io_t * io, const void *cmd, unsigned len)
     sanei_bjnp_set_timeout (io->dev, PIXMA_BULKOUT_TIMEOUT);
     error = map_error (sanei_bjnp_write_bulk (io->dev, cmd, &count));
     }
+  else if (io->interface == INT_AXIS)
+    {
+    sanei_axis_set_timeout (io->dev, PIXMA_BULKOUT_TIMEOUT);
+    error = map_error (sanei_axis_write_bulk (io->dev, cmd, &count));
+    }
   else
     {
 #ifdef HAVE_SANEI_USB_SET_TIMEOUT
@@ -536,6 +576,11 @@ pixma_read (pixma_io_t * io, void *buf, unsigned size)
     sanei_bjnp_set_timeout (io->dev, PIXMA_BULKIN_TIMEOUT);
     error = map_error (sanei_bjnp_read_bulk (io->dev, buf, &count));
     }
+  else if (io-> interface == INT_AXIS)
+    {
+    sanei_axis_set_timeout (io->dev, PIXMA_BULKIN_TIMEOUT);
+    error = map_error (sanei_axis_read_bulk (io->dev, buf, &count));
+    }
   else
     {
 #ifdef HAVE_SANEI_USB_SET_TIMEOUT
@@ -568,6 +613,11 @@ pixma_wait_interrupt (pixma_io_t * io, void *buf, unsigned size, int timeout)
       sanei_bjnp_set_timeout (io->dev, timeout);
       error = map_error (sanei_bjnp_read_int (io->dev, buf, &count));
     }
+  else if (io-> interface == INT_AXIS)
+    {
+      sanei_axis_set_timeout (io->dev, timeout);
+      error = map_error (sanei_axis_read_int (io->dev, buf, &count));
+    }
   else
     {
 #ifdef HAVE_SANEI_USB_SET_TIMEOUT
@@ -576,7 +626,8 @@ pixma_wait_interrupt (pixma_io_t * io, void *buf, unsigned size, int timeout)
       error = map_error (sanei_usb_read_int (io->dev, buf, &count));
     }
   if (error == PIXMA_EIO ||
-      (io->interface == INT_BJNP && error == PIXMA_EOF))     /* EOF is a bjnp timeout error! */
+      (io->interface == INT_BJNP && error == PIXMA_EOF) ||  /* EOF is a bjnp timeout error! */
+      (io->interface == INT_AXIS && error == PIXMA_EOF))    /* EOF is a bjnp timeout error! */
     error = PIXMA_ETIMEDOUT;	/* FIXME: SANE doesn't have ETIMEDOUT!! */
   if (error == 0)
     error = count;



-- 
Ondrej Zary



More information about the sane-devel mailing list